struct Direct { //... }
class InDirect { //... }
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.
Value type local instances are allocated on the stack. Reference type local instances are allocated on the heap.
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.
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:
class Indirect { //... public void Method(Indirect that) { RefParameter(ref this); // compile-time error OutParameter(out this); // compile-time error this = that; // compile-time error } //... }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:
struct Direct { //... public void Reassign(Direct that) { RefParameter(ref this); // compiles ok OutParameter(out this); // compiles ok this = that; // compiles ok } //... }Furthermore, you can reassign a whole struct even when the struct contains readonly fields!
struct Direct { public Direct(int value) { Field = value; } public void Reassign(Direct that) { RefParameter(ref this); // compiles ok OutParameter(out this); // compiles ok this = that; // compiles ok } public readonly int Field; } class Show { static void Main() { Direct s = new Direct(42); Console.WriteLine(s.Field); // writes 42 s.Reassign(new Direct(24)); Console.WriteLine(s.Field); // writes 24 } }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.
struct Direct { // as above } class Caller { public void Method() { Console.WriteLine(d.Field); // writes 42 d.Reassign(new Direct(24)); Console.WriteLine(d.Field); // writes 42! } private readonly Direct d = new Direct(42); } class Show { static void Main() { Caller c = new Caller(); c.Method(); } }
A struct always has a built-in public default constructor.
class DefaultConstructor { static void Eg() { Direct yes = new Direct(); // always compiles ok InDirect maybe = new InDirect(); // 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.
class NonInstantiable { private NonInstantiable() // ok { } } struct Direct { private Direct() // compile-time error { } }
A structs static constructor is not triggered by calling the structs default constructor. It is for a class.
struct Direct { static Direct() { Console.WriteLine("This is not written"); } } class NotTriggered { static void Main() { Direct local = new Direct(); } }
A struct instance cannot be null.
class Nullness { static void Eg(Direct s, Indirect c) { if (s == null) ... // compile-time error if (c == null) ... // compiles ok } }
A struct type cannot be the right hand side operand of the as operator.
class Fragment { static void Eg(Direct s, Indirect c) { Direct no = s as Direct; // compile-time error InDirect yes = c as InDirect; // compiles ok //... } }
A struct type expression cannot be the operand of a lock statement.
class LockStatement { static void Eg(Direct s, InDirect c) { lock(s) { ... } // compile-time error lock(c) { ... } // compiles ok } }
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 garabge collection.
struct Direct { ~Direct() {} // compile-time error } class InDirect { ~InDirect() {} // compiles ok }
And the CIL for ~Indirect() looks like this:
.method family hidebysig virtual instance void Finalize() cil managed { // ... } // end of method Indirect::Finalize
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:
public struct Direct { //... }
looks like this:
.class public sequential ansi sealed beforefieldinit Direct extends [mscorlib]System.ValueType { //... }
whereas the CIL for this:
public sealed class InDirect { //... }
looks like this:
.class public auto ansi sealed beforefieldinit Indirect extends [mscorlib]System.Object { //... }
You can't declare a user-defined struct type as a volatile field but you can declare a user-defined class type as a volatile field.
class Bad { private volatile Direct field; // compile-time error } class Good { private volatile Indirect field; // compiles ok }
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.
using System.Runtime.CompilerServices; class Indirect { [MethodImpl(MethodImplOptions.Synchronized)] // compiles and runs ok public void Method() { //... } } struct Direct { [MethodImpl(MethodImplOptions.Synchronized)] // compiles ok, runtime TypeLoadException public void Method() { //... } }
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:
class Bad { static void Main() { Indirect variable = new Indirect(); unsafe { fixed(Indirect * ptr = &variable) // compile-time error { //... } } } }If you want to fix an unmanaged instance you have to do so by fixing it through an unmanaged field. For example:
class Indirect { public int fixHandle; } class Bad { static void Main() { Indirect variable = new Indirect(); unsafe { fixed(int * ptr = &variable.fixHandle) // compiles ok { //... } } } }In contrast, you can (nearly) always take the address of an unmanaged instance.
struct Direct { // no reference fields at any level of nesting } class SimpleCase { static void Main() { Direct variable = new Direct(); 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.
You can only use stackalloc on unmanaged types. Hence you can never use stackalloc on class types. For example:
class Indirect { //... } class Bad { static void Main() { unsafe { Indirect * array = stackalloc Indirect[42]; // compile-time error //... } } }Where as you can use stackalloc on struct types that are unmanaged. For example:
struct Direct { // no reference fields at any level of nesting } class Good { static void Main() { unsafe { Direct * array = stackalloc Direct[42]; // compiles ok //... } } }
You can only use sizeof on unmanaged types. Hence you can never use sizeof on class types. For example:
class Indirect { //... } class Bad { static void Main() { unsafe { int size = sizeof(Indirect); // compile-time error //... } } }Where as you can use sizeof on struct types that are unmanaged. For example:
struct Direct { // no reference fields at any level of nesting } class Good { static void Main() { unsafe { int size = sizeof(Direct); // compiles ok //... } } }
The fields of a class have a default initialization to zero/false/null. The fields of a struct have no default value.
struct Direct { public int Field; } class Indirect { public Indirect() { } //... public int Field; } class Defaults { static void Main() { Direct s; Console.WriteLine(s.Field); // compile-time error Indirect c = new Indirect(); Console.WriteLine(c.Field); // compiles ok } }
You can initialize fields in a class at their point of declaration. For example:
class Indirect { //... private int field = 42; }You can't do this for fields in a struct. For example:
struct Direct { //... private int field = 42; // compile-time error }Fields in a struct have to be initialized in a constructor. For example:
struct Direct { public Direct(int value) { field = value; } //... private int field; // compiles ok }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:
struct Direct { public int X, Y; } class Example { static void Main() { Direct d; d.X = 42; Console.WriteLine(d.X); // compiles ok Console.WriteLine(d.Y); // compile-time error } }
classes inherit Object.Equals which implements identity equality whereas structs inherit ValueType.Equals which implements value equality.
using System.Diagnostics; struct Direct { public Direct(int value) { field = value; } private int field; } class Indirect { public Indirect(int value) { field = value; } private int field; } class EqualsBehavior { static void Main() { Direct s1 = new Direct(42); Direct s2 = new Direct(42); Indirect c1 = new Indirect(42); Indirect c2 = new Indirect(42); bool structEquality = s1.Equals(s2); bool classIdentity = !c1.Equals(c2); Debug.Assert(structEquality); Debug.Assert(classIdentity); } }Overriding Equals for structs should be faster because it can avoid reflection and boxing.
struct Direct { public Direct(int value) { field = value; } public override bool Equals(object other) { return other is Direct && Equals((Direct)other); } public static bool operator==(Direct lhs, Direct rhs) { return lhs.Equals(rhs); } //... private bool Equals(Direct other) { return field == other.field; } private int field; }
| |
| Jagger Software Ltd | |
| Company # 4070126 | |
| VAT # 762 5213 42 |