C# - Structs vs classes (reference vs value type) and when to use what

I never use structs - there I have said it. Not because I don't like them or feel they have no use, but I have rarely seen other developers use them. I was also years into my career before I saw them being used in a codebase. Here I am talking about structs being defined by developers, not the ones built into the language (DateTime, int etc..)

So in order to shed some light on whether what I am doing is wrong or right I wanted to take a new look at structs, which sparked this article.

Value types and reference types

There is one fundamental difference between the two, Structs are value types and classes are reference types. But what does this mean?

To begin with, there is a big difference when you do assignments in your code. Reference type assignments copies the reference where value assignments copy the value. Meaning that the more there is to copy the bigger the operations to assign to a value type is. Therefore reference types are cheaper to assign when working with large structures - as they just have to move a pointer.

Structs and classes are also allocated differently memory wise, value types go on the stack and reference types on the heap (with a pointer to it). If you are interested in memory allocation in C# I would suggest this article. For this topic -classes vs struct - the important part is that: allocations and deallocations of value types are normally faster than allocations and deallocations of reference types

The biggest difference of the two (in my mind), is that value types are passed on by copy and reference types by reference. This can lead to some unwanted results, if you are not aware of how structs work. Below I have made a small example:

static void Main(string[] args)
{
    Struct1 struct1 = new Struct1();
    struct1.I = 1;
    SetITo2(struct1);
    Console.WriteLine(struct1.I); //still 1
    Console.ReadKey();
}

public static void SetITo2(Struct1 struct1)
{
    struct1.I = 2;
}

public struct Struct1
{
    public int I { get; set; }
}

In the above I declare the variable struct1 with one property I which is an integer. I then assign the value 1 to I. The type struct1 is a value type. I then call a method that assign 2 to this variable. However this does not change the value of the i variable in the Main method. This is due to it being passed on to the SetTo2() method as a copy and not reference. We can get around this by passing it as a reference (using the ref keyword):

static void Main(string[] args)
{
    Struct1 struct1 = new Struct1();
    struct1.I = 1;
    SetITo2(ref struct1);
    Console.WriteLine(struct1.I); //now 2
    Console.ReadKey();
}

public static void SetITo2(ref Struct1 struct1)
{
    struct1.I = 2;
}

Another way to achieve this would be to wrap our variable i in a class (reference type) instead of a struct:

static void Main(string[] args)
{
    Class1 class1 = new Class1();
    class1.I = 1;
    SetITo2(class1);
    Console.WriteLine(class1.I); //now 2
    Console.ReadKey();
}

public static void SetITo2(Class1 class1)
{
    class1.I = 2;
}

public class Class1
{
    public int I { get; set; }
}

Not knowing how types are passed on (by reference or by copy) can create some odd behavior. I think this is the most important difference to know about value and reference types. In my examples

Another note is that structs should ideally also be immutable. The first of the above examples could also have been avoided, had the struct been immutable (if you had only been able to set the value once).

Structs have several limitations that classes do not. Structs cannot:

  • Derive from other structs or classes
  • Explicitly define a default parameterless constructor

You can also turn a value type into a value type and back again. This is called boxing and unboxing. An example of this would be:

int i = 0;
Object k = i;

In the above our value type i is boxed into the reference type k. Which means it is now a reference type and not a value type. I have written a more thorough article on boxing and unboxing here.

In short

You usually want to use classes. But there are a couple of exceptions to this:

  • It is immutable
  • It is small in size (< 16 bytes)
  • It will not have to be boxed and unboxed often.

That's it! I hope you enjoyed my post on structs vs classes. Let me know what you think in the comments!

Resources

For this article I have used the following resources: