C has a facility for checking dynamic assertions at run-time. It's inside
<assert.h> and its called
assert. Now
assert is a macro, so why isn't it called
ASSERT? I don't know. Prior art no doubt. Anyway,
assert is a
dynamic runtime feature, you can only use it inside functions.
/* not inside a function, won't compile :-( */
assert(sizeof(int) * CHAR_BIT >= 32);
That's a pity because it would be nice if I could get the compiler to
check things like this automatically at compile time. I've occasionally seen
an attempt at a compile-time check like this...
#if sizeof(int) * CHAR_BIT < 32
#error People of Earth. Your attention please...
#endif
But this doesn't work. The C preprocessor is a glorified text reformatter:
it knows practically nothing about C. However, there is a way to write this
as a compile time assertion (and moving any error trap to an earlier phase
is a Good Thing)
What's is going on here? Well, the example preprocesses to...
char constraint[sizeof(int) * CHAR_BIT >= 32];
If the expression is true, (an int is at least 32 bits),
the expression
will have a value of one, and constraint will be an array
of one char.
If the assertion is false, (an int is less than 32 bits),
the expression
will have a value of zero, and constraint will be an
empty array. That's
illegal, and you'll get a compile time error. Viola, a compile time assertion :-)
You can use it inside and outside a function but you can't use it twice in
the same function, as you end up with a duplicate definition.
To solve that problem you could resort to some convoluted macro trickery:
But this is pretty horrible. Also, you will probably get warnings about
unused variables. Take a step back for a moment and think about why
it works at all. It's because you have to specify the size of an array as
a compile time constant. The formal grammar of a direct-declarator
tells you this. Let's look at some bits of grammar more closely:
I just piggy backed on this, using the constraint that the value of the
constant expression cannot (in this context) be zero. A natural question
(to the curious) is are there other parts of the formal grammar that
require a constant expression. The answer, of course, is yes.
Reading the constraints of a bit field I see that if the width of a bit-field
is zero the declaration cannot have a declarator. In other words this is legal...
struct x { unsigned int : 0; };
but this is not...
struct x { unsigned int bf : 0; };
This suggests another way to create a compile time assertion
#define COMPILE_TIME_ASSERT(expr) \
struct x { unsigned int bf : expr; }
COMPILE_TIME_ASSERT(sizeof(int) * CHAR_BIT >= 32);
Trying this we again get duplicate definitions, not of a variable
this time, but of the type struct x. However we can fix
this by creating an anonymous struct:
It's well known that you can't have two case labels with the
same constant. The following will not compile...
switch (0)
{
case 0:
case 0:;
}
So, here's yet another way to create a compile time assertion. This time we
don't create a dummy variable, or a dummy type, but a dummy statement. A
dummy switch statement:
If pred evaluates to true (i.e., 1) then the case
labels will be 0 and 1.
Different; Ok. If pred evaluates to false (i.e., 0) then the
case labels
will be 0 and 0. The same; Compile time error. Viola. However, a
switch
statement cannot exist in the global scope. So the last piece of the puzzle
is to put the compile time assertions inside a function.
Events declared in a class have their += and -= access automatically locked via a lock(this) to make them
thread safe (static events are locked on the typeof the class). Events declared in a struct do not
have their += and -= access automatically locked. A lock(this) for a struct would not work since you can
only lock on a reference type expression.
Exist on stack or heap?
Value type local instances are allocated on the stack.
Reference type local instances are allocated on the heap.
Can cause garbage collection?
Creating a struct instance cannot cause a garbage collection
(unless the constructor directly or indirectly creates
a reference type instance) whereas creating a reference type
instance can cause garbage collection.
Meaning of this?
In a class, this is classified as a value, and thus cannot appear on the
left hand side of an assignment, or be used as a ref/out parameter. For example:
In a struct, this is classified as an out parameter in a constructor and as a ref parameter
in all other function members. Thus it is possible to modify the entire structure by
assigning to this or passing this as a ref/out parameter. For example:
Note however that when you call a method on a readonly value-type field, the method
call is made on a copy of the field.
structDirect{// as above}classCaller{publicvoidMethod(){Console.WriteLine(d.Field);// writes 42d.Reassign(newDirect(24));Console.WriteLine(d.Field);// writes 42!}privatereadonlyDirectd=newDirect(42);}classShow{staticvoidMain(){Callerc=newCaller();c.Method();}}
Always have a default constructor?
A struct always has a built-in public default constructor.
classDefaultConstructor{staticvoidEg(){Directyes=newDirect();// always compiles okInDirectmaybe=newInDirect();// compiles if c'tor exists and is accessible//...}}
This means that a struct is always
instantiable whereas a class might not be since all its
constructors could be private.
A struct cannot have a destructor. A destructor is just
an override of object.Finalize in disguise, and structs, being value types,
are not subject to garbage collection.
.methodfamilyhidebysigvirtualinstancevoidFinalize()cilmanaged{// ...}// end of method Indirect::Finalize
Default field layout?
The default [StructLayout] attribute (which lives in the
System.Runtime.InteropServices namespace) for a struct is LayoutKind.Sequential
whereas the default StructLayout for a class is LayoutKind.Auto. (And yes,
despite its name you can tag a class with the StructLayout attribute.)
In other words the CIL for this:
You can't use the [MethodImpl(MethodImplOptions.Synchronized)] attribute
on methods of a struct type (if you call the method you get a runtime TypeLoadException) whereas you can use
the [MethodImpl(MethodImplOptions.Synchronized)] attribute on methods of a class type.
usingSystem.Runtime.CompilerServices;classIndirect{[MethodImpl(MethodImplOptions.Synchronized)]// compiles and runs okpublicvoidMethod(){//...}}structDirect{[MethodImpl(MethodImplOptions.Synchronized)]// compiles ok, runtime TypeLoadExceptionpublicvoidMethod(){//...}}
Can be pointed to?
Clause 25.2 of the C# standard defines an unmanaged type as any type that isn't
a reference type and doesn't contain reference-type fields at any level of
nesting. That is, one of the following:
Any simple value type (11.1.3, eg byte, int, long, double, bool, etc).
Any enum type.
Any pointer type.
Any user-defined struct-type that contains fields of unmanaged types only.
You can never take the address of a instance of a type that is
not unmanaged (a fixed variable 25.3).
In contrast, you can (nearly) always take the address of an unmanaged instance.
structDirect{// no reference fields at any level of nesting}classSimpleCase{staticvoidMain(){Directvariable=newDirect();unsafe{Direct*ptr=&variable;// compiles ok//... }}}
However, you have to take the address inside a fixed statement if the variable is moveable
(subject to relocation by the garbage collector, see 25.3 and example above).
Also, you can never take the address of a volatile field.
So, in summary, you can never create a pointer to a class type but you
sometimes create a pointer to a struct type.
Can be stackalloc'd?
You can only use stackalloc on unmanaged types. Hence you can never use stackalloc on
class types. For example:
Also, the definite assignment rules of a struct are tracked on an individual
field basis. This means you can bypass initialization and "assign" the fields of
a struct one a time. For example:
a struct can't call : base() in its constructor whereas a class with no
explicit base class can.
a struct can't extend another class, a class can.
a struct can't declare protected members (eg fields, nested types) a class can.
a struct can't declare abstract function members, an abstract class can.
a struct can't declare virtual function members, a class can.
a struct can't declare sealed function members, a class can.
a struct can't declare override function members, a class can.
The one exception to this rule is that a struct can override the
virtual methods of System.Object, viz, Equals(), and GetHashCode(),
and ToString().
Equals behavior?
classes inherit Object.Equals which implements identity equality
whereas structs inherit ValueType.Equals which implements value equality.