C#: When to use struct instead of class

Recently I was learning data types in C#, and struct and class seemed to be the most similar data structure. Both of them can store nested data and have functions. But digging further down, I realized they have specific implementation and usage trade-offs.

This article is a short discussion based on my findings.

Brief Introduction to types in C#

C# has a unified type system, but they can be categorized into two distinct definition - Value type and Reference type. Variables of that are value type directly store their data, but reference type only stores the reference to their data.

By default, on assignment, passing an argument to a method, and returning a method result, variable values are copied. In the case of value-type variables, the corresponding type instances are copied.

The Difference

Value typeReference type
Allocated on the Stack or inline in the containing typeAllocated on Heap
Deallocated when the Stack unwinds or when their containing type gets deallocatedGarbage Collected
Cheaper allocations and deallocations then ClassCostly allocations and deallocations then Struct
Boxed and Unboxed on value => reference => value type castingNo boxing or unboxing occurs
Copy entire value on assignmentCopy only reference on assignment
Changes to passed value doesn’t change original dataChanges to an instance of a reference type affects all reference pointing to that instance
Can not be inheritedCan be inherited

Which one to choose

As we can see, struct behaves similar to a native data type. They are most suitable for storing data that are low-level and doesn’t need a complex state. Classes are best for maintaining complex state and data hierarchy. The use of struct is most suitable when they have all of the following characteristics -

  • ✅ It logically represents a single value, similar to primitive types (int, double, etc.).
  • ✅ It has an instance size under 16 bytes.
  • ✅ It is immutable.
  • ✅ It will not have to be boxed frequently.

One optimization we can achieve is by using in, ref, and out parameter modifiers, a value can be passed as a reference in the parameter of a function. These keywords have specific behaviors and should be used in their correct scenario.