How to Optimize C# Code with ref Structs and readonly Structs

What are ref Structs and readonly Structs?

ref structs are value types that can only exist on the stack and cannot be boxed, stored in fields of reference types, or used as generic type arguments. They’re designed for high-performance scenarios where you want to avoid heap allocations entirely. readonly structs are immutable value types where all fields are readonly, enabling compiler optimizations and preventing defensive copying in certain scenarios.

How to Use Them

Use ref structs for temporary, high-performance operations like parsing or memory manipulation. Use readonly structs when you want immutable value types that won’t be copied unnecessarily by the compiler. Both help reduce garbage collection pressure and improve performance.

Example: High-Performance String Parser

using System;
using System.Runtime.CompilerServices;
// readonly struct - immutable and optimized for passing by reference
public readonly struct Point3D
{
// All fields must be readonly in a readonly struct
public readonly double X;
public readonly double Y;
public readonly double Z;
// Constructor initializes all readonly fields
public Point3D(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
// Methods can be marked readonly to indicate they don't modify state
public readonly double DistanceFromOrigin()
{
return Math.Sqrt(X * X + Y * Y + Z * Z);
}
// Readonly structs prevent defensive copying when passed to methods
public readonly Point3D Add(in Point3D other)
{
return new Point3D(X + other.X, Y + other.Y, Z + other.Z);
}
}
// ref struct - can only exist on stack, high performance
public ref struct StringTokenizer
{
// ReadOnlySpan is itself a ref struct
private ReadOnlySpan<char> _remaining;
private readonly char _delimiter;
// Constructor takes span by reference - no copying
public StringTokenizer(ReadOnlySpan<char> input, char delimiter)
{
_remaining = input;
_delimiter = delimiter;
}
// Property to check if more tokens exist
public bool HasMore => !_remaining.IsEmpty;
// Method to get next token - returns ref struct (ReadOnlySpan)
public ReadOnlySpan<char> NextToken()
{
// If no more data, return empty span
if (_remaining.IsEmpty)
return ReadOnlySpan<char>.Empty;
// Find next delimiter position
int delimiterIndex = _remaining.IndexOf(_delimiter);
if (delimiterIndex == -1)
{
// No more delimiters - return remaining data
var result = _remaining;
_remaining = ReadOnlySpan<char>.Empty;
return result;
}
// Get token before delimiter
var token = _remaining.Slice(0, delimiterIndex);
// Update remaining to skip past delimiter
_remaining = _remaining.Slice(delimiterIndex + 1);
return token;
}
// Enumerator pattern for foreach loops
public StringTokenizer GetEnumerator() => this;
// Current property for enumerator
public ReadOnlySpan<char> Current { get; private set; }
// MoveNext for enumerator pattern
public bool MoveNext()
{
if (!HasMore)
return false;
Current = NextToken();
return true;
}
}
public class RefStructExample
{
public static void DemonstrateRefStruct()
{
// Test data
string csvData = "John,25,Engineer,Seattle,Washington";
// Create tokenizer - ref struct on stack only
var tokenizer = new StringTokenizer(csvData.AsSpan(), ',');
// Process tokens without any heap allocations
int tokenCount = 0;
while (tokenizer.HasMore)
{
var token = tokenizer.NextToken();
Console.WriteLine($"Token {tokenCount++}: {token.ToString()}");
}
}
public static void DemonstrateReadonlyStruct()
{
// Create readonly struct instances
var point1 = new Point3D(1.0, 2.0, 3.0);
var point2 = new Point3D(4.0, 5.0, 6.0);
// No defensive copying occurs with readonly structs
var distance1 = point1.DistanceFromOrigin();
var distance2 = point2.DistanceFromOrigin();
// Passing by 'in' reference prevents copying
var sum = point1.Add(in point2);
Console.WriteLine($"Point1: ({point1.X}, {point1.Y}, {point1.Z})");
Console.WriteLine($"Distance from origin: {distance1:F2}");
Console.WriteLine($"Sum: ({sum.X}, {sum.Y}, {sum.Z})");
}
// Method showing performance benefit of readonly struct
[MethodImpl(MethodImplOptions.NoInlining)]
public static double CalculateDistance(in Point3D point)
{
// 'in' parameter prevents copying the struct
return point.DistanceFromOrigin();
}
}

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top