What?

In the context off this blog post, we are using brief, differentiating definitions:

  • A class has reference semantics: its identity counts more than its value.
  • A struct has value semantics: its value matters more than its identity does, and its value is usually immutable.

When?

When do we use a struct?

Choose a class, unless there is an argument for choosing a struct. There is an argument for choosing a struct, if most of the following applies.

  • instances of the type will be fewer than 16 bytes
  • instances will be short lived (todo: why?)
  • the type represents a single value
  • the type is immutable
  • instances will not need frequent boxing

How?

How do we implement a struct?

Here is an example that meets the struct checklist. Dimensions is fewer than 16 bytes, represents a single value, and is immutable. It could also be short lived and not need frequent boxing.

public struct Dimensions
{
    // use short to decrease the size of the struct
    public readonly short Height;

    // use readonly to make the struct immutable
    public readonly short Width; 

    public readonly short Length;

    // use an enum instead of a string to decrease the size
    public readonly DimensionsUnit Units;

    // provide a constructor to set the public immutable fields
    public Dimensions(
        short height, 
        short width, 
        short length, 
        DimensionsUnit units)
    {
        Height = height;
        Width = width;
        Length = length;
        Units = units;
    }
}

public enum DimensionsUnit
{
    Centimeters,

    Inches
}

Why?

What are the reasons behind the checklist for choosing a struct?

The primary benefits of a struct are its better memory allocation performance, its better locality-of-reference, and its non-null nature (more on this later). If we can get those advantages without incurring too many costs, then a struct is a good choice. The above checklist helps us avoid the costs of a struct, and the following explains the rationale.

Allocation/deallocation

Classes are allocated to the heap and deallocated with the garbage collector. Structs are either 1. allocated inline in their containing type and deallocated with their type or 2. allocated to the stack and deallocated when the stack unwinds. Since the stack is more efficient, structs are generally cheaper to allocate and deallocated than classes are. Structs win.

Locality of reference

Arrays of classes are allocated out-of-line (i.e. array elements are references to instances in the heap). Arrays of structs are allocated inline (i.e. array elements are actual instances). Ergo, struct arrays are much cheaper to (de)allocate, and have better locality of reference. Structs win.

Non-null nature

A variable that is of a class type can hold either a reference to an instance or null. We need to check for null before we access a property, otherwise we will receive a null reference exception. Since any reference type can be null, we always have to check, even if someone already checked earlier. This can lead to a lot of null checks in an application's code base.

A variable that is of a struct type can hold an instance of that type. We can also wrap a struct in a Nullable<T> to communicate explicitly that it can contain null. If we have a normal struct, we do not have to check whether it is null. If we have a nullable struct, we can check once, and then assign it to a normal struct, after which we never again have to check it for null.

Memory usage

When cast to a class or interface, classes do not get boxed. Structs do get boxed when cast and then unboxed when cast back to a struct. Boxes are objects that live on the heap and are garbage collected; too much (un)boxing negatively impacts the heap, garbage collection, and application performance. Structs win only if they do not require frequent boxing.

Assignment

Class assignment copies the instance reference; struct assignment copies the instance data. Ergo: assignment of large types is cheaper with classes. Structs win only if they are small.

Here is a .NET Fiddle that prints the byte size of various structs. It includes some homework for those who are interested in guessing the sizes.

Pass by value vs pass by reference

This point is very similar to the point about assignment.

We pass a class by reference to its instance; any change to an instance affects every variable pointing at that instance. We pass a struct by the value of its instance; any change to a struct instance affects only one variable - the variable holding the instance that changed.

In this regard, object oriented developers are more familiar with the behavior of classes than that of structs. We expect a change to a instance in a variable to have an effect beyond the variable we changed. To prevent this confusion from arising, make structs immutable. Structs win only when they are immutable.

Here is a .NET Fiddle that illustrates the confusion. I found it in this Eric Lippert blog post.

Remaining Questions

What happens when a struct has a reference-type field?

What's really going on in that Eric Lippert example?

Is a struct the same thing is a primitive type?

References and further reading

https://msdn.microsoft.com/en-us/library/ms229017(v=vs.110).aspx

https://isocpp.org/wiki/faq/value-vs-ref-semantics