Skip to content

C# - Structure

In C#, a struct (structure) is a value type used to encapsulate small groups of related variables. They are ideal for representing data-centric, lightweight objects that do not require complex behaviors like inheritance.

Think of a struct as a blueprint for creating a single variable that holds related data points. For example, to represent an RGB color, you might need Red, Green, and Blue values. A struct can group these together:

public readonly struct RgbColor
{
public byte Red { get; }
public byte Green { get; }
public byte Blue { get; }
public RgbColor(byte r, byte g, byte b)
{
Red = r;
Green = g;
Blue = b;
}
}

Key Difference: The most critical distinction is that structs are value types and classes are reference types. When you pass a struct to a method, a copy of the struct is passed. When you pass a class instance, a reference to the instance is passed.

You define a struct using the struct keyword. Modern C# best practices encourage making structs immutable using the readonly keyword and init or get-only properties. This prevents their state from being changed after creation, leading to more predictable code.

Modern Struct Definition with a Primary Constructor (C# 12+)

Section titled “Modern Struct Definition with a Primary Constructor (C# 12+)”

C# 12 introduced primary constructors for a more concise syntax.

// Recommended modern approach (C# 12+)
public readonly struct Point(double x, double y)
{
public double X { get; } = x;
public double Y { get; } = y;
// Method within a struct
public double DistanceFromOrigin() => Math.Sqrt(X * X + Y * Y);
// Override ToString for better display
public override string ToString() => $"({X}, {Y})";
}

You can initialize a struct using the new keyword, which calls its constructor.

// Using the primary constructor from the Point struct above
var p1 = new Point(3.0, 4.0);
var p2 = new Point(-1.0, 5.5);
Console.WriteLine($"Point 1: {p1}");
Console.WriteLine($"Point 1 Distance: {p1.DistanceFromOrigin():F2}"); // F2 formats to 2 decimal places
// Output:
// Point 1: (3, 4)
// Point 1 Distance: 5.00

Note: Unlike classes, you can instantiate a struct without new, but you must initialize all its fields/properties before you can use it. Using new is generally safer as it ensures all members are initialized to their default values.

Choosing between a class and a struct is a key design decision. Use a struct when the type meets most of these criteria:

  • It logically represents a single value, like a number, a date, or a point.
  • It has a small memory footprint (generally under 16 bytes).
  • It is immutable. Once created, its value should not change.
  • It will not need to be boxed or unboxed frequently (boxing converts a value type to a reference type, which can impact performance).

Use a class for everything else, especially for larger, more complex objects that require inheritance, have identity, or whose state will change over time.

Let’s create a small program to manage book records. We’ll define a Book struct and use it in a list.

using System;
using System.Collections.Generic;
// Define an immutable Book struct
public readonly struct Book
{
public string Title { get; init; }
public string Author { get; init; }
public int PublicationYear { get; init; }
// Override ToString for a clean, readable output
public override string ToString() =>
$"'{Title}' by {Author} ({PublicationYear})";
}
// Main application logic (using top-level statements)
var library = new List<Book>
{
new() { Title = "1984", Author = "George Orwell", PublicationYear = 1949 },
new() { Title = "Dune", Author = "Frank Herbert", PublicationYear = 1965 },
new() { Title = "The Hitchhiker's Guide to the Galaxy", Author = "Douglas Adams", PublicationYear = 1979 }
};
Console.WriteLine("Welcome to the Library!");
Console.WriteLine("-----------------------");
foreach (var book in library)
{
Console.WriteLine(book);
}
// How to test this logic?
// You could write a unit test to verify that creating a Book and calling ToString()
// produces the expected string format.

When the above code is compiled and executed, it produces the following result:

Welcome to the Library!
-----------------------
'1984' by George Orwell (1949)
'Dune' by Frank Herbert (1965)
'The Hitchhiker's Guide to the Galaxy' by Douglas Adams (1979)

Structs, like classes, can implement interfaces. This is useful for providing common functionality across different types.

using System;
// Interface for objects that can be represented as a string
public interface IFormattable
{
string ToFormattedString();
}
// Struct implementing the interface
public readonly struct Person(string firstName, string lastName) : IFormattable
{
public string FirstName { get; } = firstName;
public string LastName { get; } = lastName;
// Implementing the interface method using an expression body
public string ToFormattedString() => $"Name: {LastName}, {FirstName}";
}
// --- Main Program ---
var person = new Person("John", "Doe");
Console.WriteLine(person.ToFormattedString());
// Output:
// Name: Doe, John