C# struct and class – when to use each?

C# struct and class – when to use each?

Value types and Reference types

In C# you have both Value types and Reference types. There are some fundamental differences between them that are important to understand.

The most common value types are the simple ones, like int, bool, char, and long. But also enum and struct are value types. They are called value types due to the fact that variables that are based on any of those types directly contain the values.

The second group are Reference types. Variables based on these do not contain the values directly, instead they contain a reference to the where the objects can be found. Common Reference types are classes, delegates, interfaces, and strings.

Some key differences

  • With Value types, when you assign an existing variable to a new variable the actual value gets copied. With Reference types, a new reference to the existing object is created.
  • Value types lives on the stack. They seize to exist as soon as they go out of scope. Reference types lives on the heap. They seize to exist when they are collected by the Garbage Collector.
  • Value types are not polymorphic, what you see is what you have. Reference types can be polymorphic.

Differences between a class and a struct

It is not uncommon that you can switch between defining a class and a struct just by changing a single word in the code:

// A class definition
public class MyObject
{
  ...
}

// A struct definition
public struct MyObject
{
  ...
}

By doing so you change the type from a Reference type to a Value type, which can break a lot of existing code. For example:

public MyObject CreateGreen()
{
  var obj = new MyObject();
  SetGreenProperties(obj);
  return obj;
}

private static void SetGreenProperties(MyObject obj)
{
  obj.Color = "Green";
  ...
}

If MyObject is changed from a class to a struct the object returned from CreateGreen will no longer have the green properties set. Why? Because no longer is a reference to the object sent to SetGreenProperties. Instead a new copy, only local to the called method, is created. Note however that the code will still compile and run, but the application now contains a bug that might be hard to track down.

Structs also cannot be polymorphic, in other words you cannot define a generic base struct that other structs inherit.

public class Car
{
  ...
}

// This works just fine
public class Mustang : Car
{
 ...
}

public struct Animal
{
  ...
}

// But this does not compile
public struct Lion : Animal
{
 ...
}

However, both classes and structs can implement interfaces.

When to use a struct over a class?

It might seem that a struct is just a class with limitations. So why should you bother with structs? Why not just use class only?

The, maybe not so convincing answer, is that you should use classes to define the behavior of your application and structures for storing the data that your application manipulates. Structs are Value types, they are suited for storing values. I will elaborate on this idea a bit more to build my case.

Structs will come to their best use if you make them immutable. This can be done by setting all the values in the constructor and only supplying read only properties.

public struct Address
{
  public string Line1 { get; }
  public string Line2 { get; }
  public string City { get; }
  public string State { get; }
  public int Zip { get; }

  public Address(
    string line1, 
    string line2,
    string city,
    string state,
    int zip)
  {
    Line1 = line1;
    Line2 = line2;
    City = city;
    State = state;
    Zip = zip;
  }
}

Once all fields have been assigned you can be sure the struct as a whole is valid. There is no way to alter individual fields, leaving the struct in an invalid state. If you want to update the address you simply create a new Address with the updated information and replace the old one.

public void UpdateAddress(Address newAddress)
{
  this.address = newAddress;
}

If you know a bit on how value types are handled you might be concerned that there is a lot of unnecessary copying of values going on when you pass around structs instead of just references to objects. It is true that value types get copied when you pass them to other methods, and that is why you should keep your structs quite small. The copy operations are very efficient for value types  and creating a reference type instead would involve overhead for book keeping and garbage collection.

Here is a checklist for when a struct can be suitable to use:

  • It will be used for storing data
  • You can make it immutable
  • You can limit it’s public interface to get only properties
  • There is no need for it to have subclasses
  • It can be small

Pitfalls

Structs in multi threaded systems

It is important to understand that when you assign a struct variable to another, each field gets copied, and this may not be an atomic operation. In a multi threaded system where you have a class or a static variable holding a struct and it is accessed and updated by different threads, you will need to add locks around the read and assignment operations. Even though the struct itself is immutable.

public class Person
{
  // public Address Address { get; set; } // Not thread safe

  // Somewhat thread safe, depending on usage.
  private readonly object lockObj = new object();
  private Address _address;
  public Address Address
  {
    get
    {
      lock (lockObj)
      {
        return _address;
      }
    }
    set
    {
      lock (lockObj)
      {
        _address = value;
      }
    }
  }
  // Bad usage:
  //   Console.WriteLine(person.Address.Line1);
  //   Console.WriteLine(person.Address.Line2); // Address might have been updated between these accesses
  // Good usage:
  //   var address = person.Address; // Make a local copy of the address struct
  //   Console.WriteLine(address.Line1);
  //   Console.WriteLine(address.Line2);
  ...
}

An even better alternative might be to avoid adding locks in the Person class and instead add locks in the code using it.

var p = new Person(address);
...
var newAddress = new Address(...);
lock (p)
{
  p.Address = newAddress;
}

Struct members being mutable types

Part of being immutable is also securing that your struct does not have properties that return references to mutable types.

public struct Unsecure
{
  private readonly int[] _values;
  public IEnumerable<int> Values => _values;

  public Unsecure(int[] values)
  {
    _values = values;
  }
}

var values = new [] { 1, 2, 3 };
var unsecure = new Unsecure(values);
...
values[0] = 4; // Modifies the internals of the 'unsecure' object.

You can remedy this by using ImmutableArray<T> or ImmutableList<T> from the System.Collections.Immutable namespace that I have written about in previous posts.

public struct Secure
{
  private readonly ImmutableArray<int> _values;
  public IEnumerable<int> Values => _values;

  public Secure(int[] values)
  {
    _values = values.ToImmutableArray();
  }
}

var values = new [] { 1, 2, 3 };
var secure = new Secure(values);
...
values[0] = 4; // Now this does not modify the internals of 'secure'.

Conclusion

Now you hopefully have a pretty good understanding for when structs can be a good option, and how to avoid the pitfalls involved. Most of the time you will use classes, but there are some occasions where structs, are a better fit. You should learn to identify those situations and be able to reason about why you choose one over the other.

Finally, if in doubt, use a class.