C# - Structure
C# - Structs
Section titled “C# - Structs”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.
Defining and Initializing a Struct
Section titled “Defining and Initializing a Struct”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})";}Initializing a Struct
Section titled “Initializing a Struct”You can initialize a struct using the new keyword, which calls its constructor.
// Using the primary constructor from the Point struct abovevar 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.00Note: 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.
Class vs. Struct: When to Use Which?
Section titled “Class vs. Struct: When to Use Which?”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.
Practical Application: A Mini-Project
Section titled “Practical Application: A Mini-Project”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 structpublic 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 and Interfaces
Section titled “Structs and Interfaces”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 stringpublic interface IFormattable{ string ToFormattedString();}
// Struct implementing the interfacepublic 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