Skip to main content

.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#:

csharp
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:

CharacteristicStructClass
TypeValue typeReference type
StorageStack (usually)Heap
Default valueZero-initializednull
InheritanceCannot inherit from other structs or classesCan inherit from classes
Can be inheritedNoYes
AssignmentCopies all dataCopies reference
PerformanceBetter for small data structuresBetter 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:

csharp
// 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:

csharp
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:

  1. Small, lightweight data structures (typically less than 16 bytes)
  2. Logically single values (like coordinates, RGB colors)
  3. Immutable data that doesn't change after creation
  4. High-performance scenarios where allocation overhead matters
  5. When value semantics are desired (equality based on value, not reference)

Practical Examples

Example 1: RGB Color Struct

csharp
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

csharp
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:

csharp
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

  1. Keep them small - Structs are most efficient when they're small (ideally less than 16 bytes).
  2. Consider making them immutable - This avoids issues with unexpected behavior when modifications are made.
  3. Override Equals() and GetHashCode() - For proper value equality comparison.
  4. Implement IEquatable<T> - For type-specific equality comparison.
  5. Avoid defining parameterless constructors - They don't work as you might expect in structs.
  6. 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:

  1. Numeric types: int, float, decimal, etc.
  2. DateTime and TimeSpan for time manipulation
  3. Guid for unique identifiers
  4. Nullable<T> for nullable value types
  5. KeyValuePair<TKey, TValue> for dictionary entries
  6. Span<T> and Memory<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

Exercises

  1. Create a Complex struct to represent complex numbers with real and imaginary parts.
  2. Implement a Fraction struct that represents rational numbers.
  3. Design a GeoCoordinate struct to represent a point on Earth with latitude and longitude.
  4. Create a DateRange struct that represents a period between two dates.
  5. 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! :)