Back to Blog
C#

C# Struct Declaration: Syntax, Semantics, and Performance

c# struct declaration: Learn how to declare and use structs in C#, understand value type semantics, and know when to choose a struct over a class.

structvalue typesC# syntaxmemory allocationperformance
Diagram showing a C# struct declaration with value type semantics and memory allocation.

c# struct declaration requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

Declaring a Struct in C#

In C#, a struct is a value type that can encapsulate data and related functionality. The basic declaration uses the struct keyword:

public struct Point { public int X; public int Y; }

This declares a Point struct with two public integer fields. You can also define properties, methods, and constructors inside a struct. Unlike classes, struct instances are typically allocated on the stack or inline within containing objects, which affects their copying and lifetime behavior.

The declaration syntax is similar to a class, but the semantics differ in fundamental ways. A struct cannot inherit from another struct or class, and it cannot be the base of a class. It implicitly inherits from System.ValueType, which in turn inherits from System.Object.

Value Type Semantics and Copying

When you assign a struct variable to another, the entire value is copied. This is different from references, where only the reference is copied.

Point a = new Point { X = 1, Y = 2 }; Point b = a; b.X = 10; Console.WriteLine(a.X); // Still 1

Because Point is a struct, b gets a full copy of a's data. Changing b.X does not affect a. This behavior is essential when you want to ensure that data passed to methods cannot be modified unintentionally. However, it also means that large structs can cause performance overhead when copied frequently.

When to Use a Struct vs a Class

The decision between struct and class depends on the data size, usage pattern, and required semantics. Use a struct when:

  • The instance is small and short-lived.
  • The value is logically a single, indivisible unit.
  • You want value semantics, where equality is based on the data rather than identity.
  • You need to avoid heap allocation and garbage collection pressure.

A class is more appropriate when:

  • The object is large or contains many fields.
  • You need reference identity or shared references.
  • You need inheritance or polymorphism.
  • The object is frequently stored in collections that expect reference types.

A common guideline is to use a struct for types under 16–24 bytes, but that is not a hard rule. Measure and profile when performance matters.

Struct Constructors and Initialization Rules

A struct cannot have a parameterless constructor defined explicitly. In older C# versions, the default constructor always zero-initializes all fields. Starting with C# 10, you can define a parameterless constructor for a struct, but it must assign all fields.

public struct Temperature { public double Celsius; public Temperature(double celsius) => Celsius = celsius; // C# 10 allows this: public Temperature() => Celsius = 0; }

If you define a constructor, you must initialize all fields. The compiler enforces this. Also, a struct cannot have instance field initializers unless you define a constructor that runs them.

readonly struct and ref struct

C# provides modifiers to control struct behavior. A readonly struct ensures that all fields are readonly, which prevents accidental mutation and enables the compiler to avoid defensive copies when accessing members.

public readonly struct Distance { public readonly int Meters; public Distance(int meters) => Meters = meters; }

A ref struct is a special kind of struct that is allocated on the stack and cannot be boxed or used in async methods. It is used for high-performance scenarios like Span<T>.

public ref struct MyRefStruct { public int Value; }

Ref structs cannot be captured by closures or used in async methods because they might escape the stack.

Performance Implications of Structs

Structs can reduce heap allocations and garbage collection overhead. When you create an array of structs, the memory is contiguous, improving cache locality. However, copying a large struct is more expensive than copying a reference. Also, boxing occurs when you convert a struct to an interface or object, which allocates on the heap.

Point p = new Point(1, 2); object boxed = p; // Boxing

Boxing is a performance hit and should be avoided in hot paths. Using generics with structs avoids boxing, as the runtime can specialize the code.

Common Mistakes and Pitfalls

One common mistake is mutating a struct through a property or method when it is stored in a read-only context. For example, if you have a readonly field of a struct type, calling a method that modifies its fields will cause a compiler error because the compiler cannot guarantee the method doesn't mutate the struct.

Another issue is using a struct when reference semantics are needed. If you pass a struct to a method and expect the method to modify the original, you must use the ref or out modifier.

public void Modify(ref Point p) { p.X = 100; }

Without ref, the method receives a copy and changes are lost.

Structs in Collections and Async Code

When you store structs in collections like List<T>, the collection holds the struct instances directly. Modifying an element requires using the indexer with a local variable or the ref return. For example:

List<Point> points = new List<Point>(); points.Add(new Point(1, 2)); // To modify: Point temp = points[0]; temp.X = 5; points[0] = temp;

In async methods, you cannot use ref or out parameters, and you cannot store a ref struct in a field. This limits the use of certain struct types in asynchronous code.

c# struct declaration: Practical Usage and Code Examples | RYUSLOG DEV