

Ahh, C# structs: a classic carryover from the C/C++ days. Have you ever used one? Wondering how they work? Well, they are similar to classes with some substantial differences. Structs have niche value to us as C# developers. You don’t often need them, but there are some things they do better than classes.
They also come with some caveats. Let me walk you through all this through a series of code examples.
What Are Structs?
Structs are the little sisters of class objects. At first glance, you may not even be able to tell the difference between them. They can both contain a collection of fields of varying visibility, as seen here:
public struct Flower { public string name; protected int beautyRating; private bool isVascular; }
You can treat structs?as a simple data structure where you are just using them as a field bag. Maybe you use them as a data transfer object, or maybe you want to perform some logic elsewhere based on the field values.
You can also use them to encapsulate behavior, like so:
public struct FlowerWithBehavior { string name; int beautyRating; bool isVascular; public void makeBeautiful() { beautyRating = 10; } }
So far, structs look like classes, just with a different keyword. That being said, they still differ from classes in some significant ways.
What Makes Structs Different?
One of the most significant differences between structs and classes is that structs use value equality instead of reference equality. Take a similar class and struct:
public struct FlowerStruct { string name; int beautyRating; bool isVascular; public FlowerStruct(string name, int beautyRating, bool isVascular) { this.name = name; this.beautyRating = beautyRating; this.isVascular = isVascular; } } public class FlowerClass { public string name; public int beautyRating; public bool isVascular; public FlowerClass(string name, int beautyRating, bool isVascular) { this.name = name; this.beautyRating = beautyRating; this.isVascular = isVascular; } }
Though similar, these two will have different equalities:
[Fact] public void Test_Struct_Vs_Class_Equality() { FlowerStruct struct1 = new FlowerStruct("Daisy", 3, true); FlowerStruct struct2 = new FlowerStruct("Daisy", 3, true); FlowerClass class1 = new FlowerClass("Daisy", 3, true); FlowerClass class2 = new FlowerClass("Daisy", 3, true); Assert.Equal(struct1, struct2); Assert.NotEqual(class1, class2); }
As you can see, the structs are equal as long as the fields inside are equal, whereas the classes are not equal, no matter the fields. This is perhaps the greatest difference between the structs and classes. And it’s nice: with structs, you get value equality out of the box. You could use them to create value objects. (There is one caveat; more on that later.)
Also, structs can never be null:
[Fact] public void Test_Struct_Not_Null( { Flower flowerStruct = null; //<-- Compile error. }
Pretty neat, considering null reference errors are the number one cause of bugs in software.
Of course, you can cheat a little and make them nullable:
Beware of Constructor Challenges
As cool as all this is, structs aren’t all roses and rainbows. If you want to use them as immutable value objects, for example, you still need a parameter-less constructor. This means your value objects could be in an invalid state:
public struct StrictFlower { public string name; public int beautyRating; public StrictFlower(string name, int beautyRating) { this.name = name; if (name == "daisy") this.beautyRating = beautyRating; else this.beautyRating = 5; } } [Fact] public void Struct_Always_Has_Parameterless_Constructor() { StrictFlower flower = new StrictFlower(); flower.name = "daisy"; int notFive = 3; flower.beautyRating = notFive; }
Because of this, you cannot guarantee the class will initially be valid. If you want to ensure your struct is always in a valid state, you may need to have your team members avoid using parameter-less constructors for their structs.
Beware of Inheritance Limits
Structs automatically inherit from the ValueType class, a child of object. But you cannot inherit from them:
public struct InheritingFlower : Flower //<- Compile Error { }
That said, you can implement interfaces on them:
public struct ImplementingFlower : IComparable<ImplementingFlower> { string name; int beautyRating; public int CompareTo(ImplementingFlower other) { return beautyRating.CompareTo(other.beautyRating); } }
The Best Way to Use Structs
Structs really shine as simple data structures. It is much simpler to spin up a struct when you need to represent data transfer objects, input objects, result objects, or other holders of information. When using them as simple structures, don’t bother wrapping them in properties:
public struct PropertyFlower { string name; int beautyRating; bool isVascular; public string Name { get => name; set => name = value; } public int BeautyRating { get => beautyRating; set => beautyRating = value; } public bool IsVascular { get => isVascular; set => isVascular = value; } }
Instead, let the fields run free:
public struct FieldFlower { public string name; public int beautyRating; public bool isVascular; }
Structs also can also have?some value as value objects when creating a rich domain model. However, when doing this, you must be aware of the pitfalls with its parameter-less constructor.
Be sure to avoid using structs as a collecting parameter. If you pass a struct to a method, it is passed by value and a copy of the struct is passed, not a pointer to the struct. You can see what I mean here:
class StructPass { public void changeName(Flower flower) { flower.name = "different"; } } [Fact] public void Structs_Pass_By_Value() { Flower flower = new Flower("daisy", 3, true); new StructPass().changeName(flower); Assert.NotEqual("different", flower.name); }
Structs: Class Objects’ Little Siblings
As I said, structs are the little sibling to class objects. They don’t get to hang out with the other classes as much; they’re rarely used. But it is clear that they have their uses.
Structs come with built-in equality. They cannot be null. And they are the easiest way to make new simple data structures. If you are careful with them, you can get some bang for your buck using them in your application.
Tools at your disposal
SubMain offers CodeIt.Right that easily integrates into Visual Studio for flexible and intuitive automated code review solution that works real-time, on demand, at the source control check-in or as part of your build.