Legacy Guidance:This article preserves historical web development content. For modern .NET 8+ best practices, visit our Tutorials section.
Structs in C# can do most of what classes can do, but there are important differences that affect how you should design and use them. Understanding these guidelines will help you write more efficient, maintainable code and avoid common pitfalls.
The key question isn't whether structs are better or worse than classes—it's knowing when each is the right tool for the job. Let's start by understanding when you should choose a struct over a class.
When to Use Structs
You should consider using a struct when your type meets these criteria:
Small Size: Instances are relatively small (typically 16 bytes or less)
Short Lifetime: The instance lifetime is very short
Embedded Usage: You'll embed instances into other objects or collections
Single Value: Logically represents a single value (like Point, Color, or Rectangle)
Common examples of good struct candidates include coordinates (X, Y), colors (R, G, B, A), dates and times, and small mathematical types. If your type doesn't fit these criteria, you're probably better off using a class.
Essential Design Guidelines
Represent a Single Value
Logically, your struct should represent one value. Think of types like int or decimal—they represent single values. Your struct should follow the same principle. A Point struct with X and Y coordinates represents one concept: a location.
Keep It Small
Keep your struct size under 16 bytes. Larger structs lose the performance benefits of being value types. When structs get too big, copying them becomes expensive, and you negate the advantage of stack allocation.
Make It Immutable
Design your struct to be immutable whenever possible. This means once you create it, you can't change its values. Immutable structs are safer, more predictable, and work better in multithreaded scenarios.
No Inheritance
Structs cannot inherit from other structs or classes, and they cannot be base types. This is a fundamental limitation. If you need inheritance, you must use a class instead.
Cannot Be Static
You cannot declare a struct as static. If you need a static type, use a static class instead.
Understanding Value Type Behavior
When you pass a struct to a method, it's passed by value—meaning the method gets a copy, not a reference to the original. This has important implications:
Value vs Reference Behavior
public struct Point
{
public int X;
public int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}
public void ModifyPoint(Point p)
{
p.X = 100; // Only modifies the local copy
}
// Usage
Point myPoint = new Point(10, 20);
ModifyPoint(myPoint);
Console.WriteLine(myPoint.X); // Still 10, not 100!
The original myPoint remains unchanged because ModifyPoint received a copy. If you need to modify the original, pass the struct by reference using the ref keyword.
Boxing and Unboxing Performance
Structs are value types, which means allocating and deallocating them is faster than reference types. However, assigning a struct to an object variable requires boxing, and extracting it back requires unboxing. This creates performance overhead.
Boxing Example
Point p = new Point(5, 10);
// Boxing - struct copied to heap
object obj = p;
// Unboxing - copied back from heap
Point p2 = (Point)obj;
Only use structs when you expect minimal boxing and unboxing. If your struct will frequently be cast to interfaces or object, the boxing overhead might make a class a better choice.
Constructor Restrictions
You cannot explicitly define a parameterless constructor for a struct. C# doesn't allow it because the runtime provides one automatically that initializes all fields to their default values:
Constructor Error Example
public struct SampleStruct
{
int member1;
// This is allowed - constructor with parameters
public SampleStruct(int data)
{
member1 = data;
}
// ERROR: Structs cannot contain explicit parameterless constructors
public SampleStruct()
{
Console.WriteLine("Default Constructor");
}
}
The compiler will give you an error: "Structs cannot contain explicit parameterless constructors." This restriction ensures that all struct instances have predictable default values.
Field Initialization Rules
You cannot directly initialize struct fields in their declarations. This is different from classes:
Field Initialization Error
// ERROR: Cannot have instance field initializers in structs
public struct SampleStruct
{
int member1 = 10; // Not allowed!
int member2 = 134; // Not allowed!
}
// CORRECT: Initialize in constructor
public struct SampleStruct
{
int member1;
int member2;
public SampleStruct(int m1, int m2)
{
member1 = m1;
member2 = m2;
}
}
While classes allow field initialization at declaration, structs require you to initialize fields in constructors. This ensures proper initialization order and consistent behavior.
Handling Default Values
Always check that struct fields are in a valid state before using them. Reference type fields default to null, which can cause runtime exceptions if you're not careful:
Safe Struct Design
public struct SampleStruct
{
string member;
public SampleStruct(string data)
{
member = data;
}
public bool CompareString(string data)
{
// Always check both parameters AND member fields
if (data != null && data.Length > 0 &&
member != null && member.Length > 0)
{
return member.Equals(data);
}
return false;
}
}
// Dangerous usage
SampleStruct obj1 = new SampleStruct(); // member is null!
bool isEqual = obj1.CompareString("Sample"); // Could crash without null checks
Even though you have a constructor that sets the member, someone can still create the struct using the default parameterless constructor. Your methods must handle fields being in their default state.
Implementing IEquatable
For structs, implement IEquatable<T> to provide efficient equality comparisons. This interface eliminates boxing and reflection overhead compared to using the default Equals method:
IEquatable Implementation
public struct Point : IEquatable
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
// Efficient equality comparison without boxing
public bool Equals(Point other)
{
return X == other.X && Y == other.Y;
}
// Override Object.Equals for consistency
public override bool Equals(object obj)
{
return obj is Point other && Equals(other);
}
// Also override GetHashCode when overriding Equals
public override int GetHashCode()
{
return HashCode.Combine(X, Y);
}
}
Summary
Structs are powerful when used correctly, but they come with specific rules and limitations. Keep them small, immutable, and use them to represent single values. Understand the performance implications of boxing and unboxing, and always account for default values when working with struct fields.
Following these design guidelines will help you write efficient, maintainable code that leverages structs where they shine while avoiding common pitfalls that can lead to bugs and performance issues.
FAQ
What's the main difference between structs and classes in C#?
Structs are value types allocated on the stack, while classes are reference types allocated on the heap. Structs are passed by value (copied), classes are passed by reference. Structs cannot inherit from other structs or classes, and they have different performance characteristics.
Why can't I define a parameterless constructor in a struct?
C# doesn't allow explicit parameterless constructors in structs because the runtime automatically provides one that initializes all fields to their default values. This ensures struct instances are always in a valid state, even when created without calling a constructor.
What is boxing and why does it matter for structs?
Boxing is when a value type (struct) is converted to a reference type (object), allocating memory on the heap. Unboxing is the reverse. Frequent boxing/unboxing operations can hurt performance. Use structs when you'll have minimal boxing, as this is where they perform best.
Should structs be immutable?
Yes, Microsoft recommends making structs immutable. Immutable structs are safer to use, avoid surprising behavior when passing by value, and work better in multithreaded scenarios. Make fields readonly and don't provide setters on properties.
When should I use a struct instead of a class?
Use structs for small, lightweight types (typically under 16 bytes) that logically represent a single value, have a short lifetime, are rarely boxed, and don't need inheritance. Common examples include coordinates, color values, and small mathematical types.