.NET Structs
Introduction
In .NET, a struct (short for structure) is a value type that can encapsulate data and related functionality. Unlike classes, which are reference types, structs are stored directly on the stack or inline in containing types. This fundamental difference affects how structs behave in memory management, assignment operations, and performance characteristics.
Structs are particularly useful when you need small, lightweight data structures that primarily store data and have limited behavior. They are commonly used for representing simple concepts such as coordinates, colors, or small data points.
What is a Struct in .NET?
A struct in .NET is a value type that can contain:
- Data members (fields)
- Methods
- Properties
- Indexers
- Constructors
- Events
- Nested types
- Operator methods
The key characteristic of structs is that they are value types, meaning they store their data directly within the variable's memory allocation.
Declaring a Struct
Let's start with a simple example of how to declare a struct in C#:
public struct Point
{
// Fields
public int X;
public int Y;
// Constructor
public Point(int x, int y)
{
X = x;
Y = y;
}
// Method
public double DistanceFromOrigin()
{
return Math.Sqrt(X * X + Y * Y);
}
// Override ToString method
public override string ToString()
{
return $"({X}, {Y})";
}
}
Structs vs Classes
Understanding the differences between structs and classes is crucial for using them effectively:
Characteristic | Struct | Class |
---|---|---|
Type | Value type | Reference type |
Storage | Stack (usually) | Heap |
Default value | Zero-initialized | null |
Inheritance | Cannot inherit from other structs or classes | Can inherit from classes |
Can be inherited | No | Yes |
Assignment | Copies all data | Copies reference |
Performance | Better for small data structures | Better for larger data structures |
Using Structs
Creating and Using a Struct
Here's how you can create and use the Point
struct we defined earlier:
// Create a struct instance using the parameterized constructor
Point p1 = new Point(3, 4);
// Create a struct instance using object initializer syntax
Point p2 = new Point { X = 5, Y = 7 };
// Access fields and call methods
Console.WriteLine($"Point p1: {p1}");
Console.WriteLine($"Distance from origin: {p1.DistanceFromOrigin()}");
// Output:
// Point p1: (3, 4)
// Distance from origin: 5
Value Semantics
One of the key aspects of structs is their value semantics. When you assign a struct to another variable or pass it to a method, a complete copy of the struct is made:
Point original = new Point(1, 2);
Point copy = original; // A complete copy is made
// Changing the copy doesn't affect the original
copy.X = 10;
Console.WriteLine($"Original: {original}"); // Outputs: Original: (1, 2)
Console.WriteLine($"Copy: {copy}"); // Outputs: Copy: (10, 2)
When to Use Structs
Structs are best used in the following scenarios:
- Small, lightweight data structures (typically less than 16 bytes)
- Logically single values (like coordinates, RGB colors)
- Immutable data that doesn't change after creation
- High-performance scenarios where allocation overhead matters
- When value semantics are desired (equality based on value, not reference)
Practical Examples
Example 1: RGB Color Struct
public struct RgbColor
{
public byte R;
public byte G;
public byte B;
public RgbColor(byte r, byte g, byte b)
{
R = r;
G = g;
B = b;
}
public string ToHexString()
{
return $"#{R:X2}{G:X2}{B:X2}";
}
}
// Usage:
RgbColor red = new RgbColor(255, 0, 0);
Console.WriteLine(red.ToHexString()); // Outputs: #FF0000
Example 2: Money Value Type
public struct Money
{
public decimal Amount { get; }
public string Currency { get; }
public Money(decimal amount, string currency)
{
Amount = amount;
Currency = currency;
}
public override string ToString()
{
return $"{Amount.ToString("0.00")} {Currency}";
}
// You can add operators to make calculations easier
public static Money operator +(Money a, Money b)
{
if (a.Currency != b.Currency)
throw new InvalidOperationException("Cannot add different currencies");
return new Money(a.Amount + b.Amount, a.Currency);
}
}
// Usage:
Money payment1 = new Money(10.50m, "USD");
Money payment2 = new Money(5.75m, "USD");
Money total = payment1 + payment2;
Console.WriteLine(total); // Outputs: 16.25 USD
Readonly Structs
Starting with C# 7.2, you can create readonly
structs to enforce immutability:
public readonly struct Temperature
{
public readonly double Celsius { get; }
public Temperature(double celsius)
{
Celsius = celsius;
}
public double Fahrenheit => Celsius * 9/5 + 32;
public override string ToString() => $"{Celsius}°C ({Fahrenheit}°F)";
}
// Usage:
Temperature freezing = new Temperature(0);
Console.WriteLine(freezing); // Outputs: 0°C (32°F)
Best Practices for Using Structs
- Keep them small - Structs are most efficient when they're small (ideally less than 16 bytes).
- Consider making them immutable - This avoids issues with unexpected behavior when modifications are made.
- Override
Equals()
andGetHashCode()
- For proper value equality comparison. - Implement
IEquatable<T>
- For type-specific equality comparison. - Avoid defining parameterless constructors - They don't work as you might expect in structs.
- Don't use structs just for performance - Measure actual impact before optimization.
Common Built-in .NET Structs
.NET includes many built-in structs that you use regularly:
- Numeric types:
int
,float
,decimal
, etc. DateTime
andTimeSpan
for time manipulationGuid
for unique identifiersNullable<T>
for nullable value typesKeyValuePair<TKey, TValue>
for dictionary entriesSpan<T>
andMemory<T>
for modern memory management
Summary
.NET structs are powerful value types that offer unique benefits when used appropriately. They excel at representing small, logically single values with value semantics. By understanding when and how to use structs, you can write more efficient and cleaner code.
Remember these key points:
- Structs are value types stored on the stack
- They are copied on assignment
- They're ideal for small, immutable data structures
- They cannot inherit from other types but can implement interfaces
Additional Resources
- Microsoft Docs: Structs (C# Programming Guide)
- Microsoft Docs: Value Types
- C# Language Specification: Structs
Exercises
- Create a
Complex
struct to represent complex numbers with real and imaginary parts. - Implement a
Fraction
struct that represents rational numbers. - Design a
GeoCoordinate
struct to represent a point on Earth with latitude and longitude. - Create a
DateRange
struct that represents a period between two dates. - Implement a
Vector2D
struct with basic vector operations like addition, subtraction, and scalar multiplication.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)