C#idiomatic C#
ref-structcsharp/language-reference/builtin-types

Language

`ref` structure types (C# reference)

Learn about the ref struct type in C#

  • page csharp/language-reference/builtin-types/ref-struct
  • updated 01/14/2026

Use the ref modifier when declaring a structure type. You allocate instances of a ref struct type on the stack, and they can't escape to the managed heap. To ensure this property, the compiler limits the usage of ref struct types as follows:

  • You can't use a ref struct as the element type of an array.
  • You can't declare a ref struct as the type of a field in a class or a non-ref struct.
  • You can't box a ref struct to System.ValueType or System.Object.
  • You can't capture a ref struct variable in a lambda expression or a local function.
  • Before C# 13, you can't use ref struct variables in an async method. Beginning with C# 13, a ref struct variable can't be used in the same block as the await expression in an async method. However, you can use ref struct variables in synchronous methods, for example, in methods that return System.Threading.Tasks.Task or System.Threading.Tasks.Task.
  • Before C# 13, you can't use a ref struct variable in iterators. Beginning with C# 13, ref struct types and ref locals can be used in iterators, provided they aren't in code segments with the yield return statement.
  • Before C# 13, a ref struct can't implement interfaces. Beginning with C# 13, a ref struct can implement interfaces, but must adhere to the ref safety rules. For example, a ref struct type can't be converted to the interface type because that requires a boxing conversion.
  • Before C# 13, a ref struct can't be a type argument. Beginning with C# 13, a ref struct can be the type argument when the type parameter specifies the allows ref struct in its where clause.

ms.custom: "updateeachrelease"

The C# language reference documents the most recently released version of the C# language. It also contains initial documentation for features in public previews for the upcoming language release.

The documentation identifies any feature first introduced in the last three versions of the language or in current public previews.

Typically, you define a ref struct type when you need a type that also includes data members of ref struct types:

sample · csharpdocs/csharp/language-reference/builtin-types/snippets/shared/StructType.csSnippetRefStruct

csharp
namespace builtin_types
{
    public static class StructType
    {
        public static void Examples()
        {
            without_new.StructWithoutNew.Main();
            parameterless_constructor.Example.Main();
            field_initializer.Example.Main();
            with_expression.Example.Main();
        }

        // <SnippetStructExample>
        public struct Coords
        {
            public Coords(double x, double y)
            {
                X = x;
                Y = y;
            }

            public double X { get; }
            public double Y { get; }

            public override string ToString() => $"({X}, {Y})";
        }
        // </SnippetStructExample>
    }

    namespace without_new
    {
        // <SnippetWithoutNew>
        public static class StructWithoutNew
        {
            public struct Coords
            {
                public double x;
                public double y;
            }

            public static void Main()
            {
                Coords p;
                p.x = 3;
                p.y = 4;
                Console.WriteLine($"({p.x}, {p.y})");  // output: (3, 4)
            }
        }
        // </SnippetWithoutNew>
    }

    namespace readonly_struct
    {
        // <SnippetReadonlyStruct>
        public readonly struct Coords
        {
            public Coords(double x, double y)
            {
                X = x;
                Y = y;
            }

            public double X { get; init; }
            public double Y { get; init; }

            public override string ToString() => $"({X}, {Y})";
        }
        // </SnippetReadonlyStruct>
    }

    namespace readonly_members
    {
        public struct Example
        {
            // <SnippetReadonlyProperty>
            private int counter;
            public int Counter
            {
                readonly get => counter;
                set => counter = value;
            }
            // </SnippetReadonlyProperty>

            // <ReadonlyWithInit>
            public readonly double X { get; init; }
            // </ReadonlyWithInit>
            public readonly double Y { get; init; }

            // <SnippetReadonlyMethod>
            public readonly double Sum()
            {
                return X + Y;
            }
            // </SnippetReadonlyMethod>

            // <SnippetReadonlyOverride>
            public readonly override string ToString() => $"({X}, {Y})";
            // </SnippetReadonlyOverride>
        }
    }

    namespace ref_struct
    {
        // <SnippetRefStruct>
        public ref struct CustomRef
        {
            public bool IsValid;
            public Span<int> Inputs;
            public Span<int> Outputs;
        }
        // </SnippetRefStruct>

        // <SnippetReadonlyRef>
        public readonly ref struct ConversionRequest
        {
            public ConversionRequest(double rate, ReadOnlySpan<double> values)
            {
                Rate = rate;
                Values = values;
            }

            public double Rate { get; }
            public ReadOnlySpan<double> Values { get; }
        }
        // </SnippetReadonlyRef>

        // <SnippetRefField>
        public ref struct RefFieldExample
        {
            private ref int number;

            public int GetNumber()
            {
                if (System.Runtime.CompilerServices.Unsafe.IsNullRef(ref number))
                {
                    throw new InvalidOperationException("The number ref field is not initialized.");
                }

                return number;
            }
        }
        // </SnippetRefField>
    }

    namespace parameterless_constructor
    {
        public static class Example
        {
            // <ParameterlessConstructor>
            public readonly struct Measurement
            {
                public Measurement()
                {
                    Value = double.NaN;
                    Description = "Undefined";
                }

                public Measurement(double value, string description)
                {
                    Value = value;
                    Description = description;
                }
            
                public double Value { get; init; }
                public string Description { get; init; }
            
                public override string ToString() => $"{Value} ({Description})";
            }

            public static void Main()
            {
                var m1 = new Measurement();
                Console.WriteLine(m1);  // output: NaN (Undefined)

                var m2 = default(Measurement);
                Console.WriteLine(m2);  // output: 0 ()

                var ms = new Measurement[2];
                Console.WriteLine(string.Join(", ", ms));  // output: 0 (), 0 ()
            }
            // </ParameterlessConstructor>
        }
    }

    namespace field_initializer
    {
        public static class Example
        {
            // <FieldInitializer>
            public readonly struct Measurement
            {
                public Measurement(double value)
                {
                    Value = value;
                }

                public Measurement(double value, string description)
                {
                    Value = value;
                    Description = description;
                }

                public Measurement(string description)
                {
                    Description = description;
                }
            
                public double Value { get; init; }
                public string Description { get; init; } = "Ordinary measurement";
            
                public override string ToString() => $"{Value} ({Description})";
            }

            public static void Main()
            {
                var m1 = new Measurement(5);
                Console.WriteLine(m1);  // output: 5 (Ordinary measurement)

                var m2 = new Measurement();
                Console.WriteLine(m2);  // output: 0 ()

                var m3 = default(Measurement);
                Console.WriteLine(m3);  // output: 0 ()
            }
            // </FieldInitializer>
        }
    }

    namespace with_expression
    {
        public static class Example
        {
            // <WithExpression>
            public readonly struct Coords
            {
                public Coords(double x, double y)
                {
                    X = x;
                    Y = y;
                }

                public double X { get; init; }
                public double Y { get; init; }

                public override string ToString() => $"({X}, {Y})";
            }

            public static void Main()
            {
                var p1 = new Coords(0, 0);
                Console.WriteLine(p1);  // output: (0, 0)

                var p2 = p1 with { X = 3 };
                Console.WriteLine(p2);  // output: (3, 0)

                var p3 = p1 with { X = 1, Y = 4 };
                Console.WriteLine(p3);  // output: (1, 4)
            }
            // </WithExpression>
        }
    }

    namespace InlineArrays
    {
        // <DeclareInlineArray>
        [System.Runtime.CompilerServices.InlineArray(10)]
        public struct CharBuffer
        {
            private char _firstElement;
        }
        // </DeclareInlineArray>

        // <DeclareInlineArrayWithPointer>
        [System.Runtime.CompilerServices.InlineArray(10)]
        public struct CharBufferWithPointer
        {
            private unsafe char* _pointerElement;    // CS9184
        }
        // </DeclareInlineArrayWithPointer>

        // <DeclareInlineArrayWithReferenceType>
        [System.Runtime.CompilerServices.InlineArray(10)]
        public struct CharBufferWithReferenceType
        {
            private string _referenceElement;
        }
        // </DeclareInlineArrayWithReferenceType>
    }
}

To declare a ref struct as readonly, combine the readonly and ref modifiers in the type declaration (the readonly modifier must come before the ref modifier):

sample · csharpdocs/csharp/language-reference/builtin-types/snippets/shared/StructType.csSnippetReadonlyRef

csharp
namespace builtin_types
{
    public static class StructType
    {
        public static void Examples()
        {
            without_new.StructWithoutNew.Main();
            parameterless_constructor.Example.Main();
            field_initializer.Example.Main();
            with_expression.Example.Main();
        }

        // <SnippetStructExample>
        public struct Coords
        {
            public Coords(double x, double y)
            {
                X = x;
                Y = y;
            }

            public double X { get; }
            public double Y { get; }

            public override string ToString() => $"({X}, {Y})";
        }
        // </SnippetStructExample>
    }

    namespace without_new
    {
        // <SnippetWithoutNew>
        public static class StructWithoutNew
        {
            public struct Coords
            {
                public double x;
                public double y;
            }

            public static void Main()
            {
                Coords p;
                p.x = 3;
                p.y = 4;
                Console.WriteLine($"({p.x}, {p.y})");  // output: (3, 4)
            }
        }
        // </SnippetWithoutNew>
    }

    namespace readonly_struct
    {
        // <SnippetReadonlyStruct>
        public readonly struct Coords
        {
            public Coords(double x, double y)
            {
                X = x;
                Y = y;
            }

            public double X { get; init; }
            public double Y { get; init; }

            public override string ToString() => $"({X}, {Y})";
        }
        // </SnippetReadonlyStruct>
    }

    namespace readonly_members
    {
        public struct Example
        {
            // <SnippetReadonlyProperty>
            private int counter;
            public int Counter
            {
                readonly get => counter;
                set => counter = value;
            }
            // </SnippetReadonlyProperty>

            // <ReadonlyWithInit>
            public readonly double X { get; init; }
            // </ReadonlyWithInit>
            public readonly double Y { get; init; }

            // <SnippetReadonlyMethod>
            public readonly double Sum()
            {
                return X + Y;
            }
            // </SnippetReadonlyMethod>

            // <SnippetReadonlyOverride>
            public readonly override string ToString() => $"({X}, {Y})";
            // </SnippetReadonlyOverride>
        }
    }

    namespace ref_struct
    {
        // <SnippetRefStruct>
        public ref struct CustomRef
        {
            public bool IsValid;
            public Span<int> Inputs;
            public Span<int> Outputs;
        }
        // </SnippetRefStruct>

        // <SnippetReadonlyRef>
        public readonly ref struct ConversionRequest
        {
            public ConversionRequest(double rate, ReadOnlySpan<double> values)
            {
                Rate = rate;
                Values = values;
            }

            public double Rate { get; }
            public ReadOnlySpan<double> Values { get; }
        }
        // </SnippetReadonlyRef>

        // <SnippetRefField>
        public ref struct RefFieldExample
        {
            private ref int number;

            public int GetNumber()
            {
                if (System.Runtime.CompilerServices.Unsafe.IsNullRef(ref number))
                {
                    throw new InvalidOperationException("The number ref field is not initialized.");
                }

                return number;
            }
        }
        // </SnippetRefField>
    }

    namespace parameterless_constructor
    {
        public static class Example
        {
            // <ParameterlessConstructor>
            public readonly struct Measurement
            {
                public Measurement()
                {
                    Value = double.NaN;
                    Description = "Undefined";
                }

                public Measurement(double value, string description)
                {
                    Value = value;
                    Description = description;
                }
            
                public double Value { get; init; }
                public string Description { get; init; }
            
                public override string ToString() => $"{Value} ({Description})";
            }

            public static void Main()
            {
                var m1 = new Measurement();
                Console.WriteLine(m1);  // output: NaN (Undefined)

                var m2 = default(Measurement);
                Console.WriteLine(m2);  // output: 0 ()

                var ms = new Measurement[2];
                Console.WriteLine(string.Join(", ", ms));  // output: 0 (), 0 ()
            }
            // </ParameterlessConstructor>
        }
    }

    namespace field_initializer
    {
        public static class Example
        {
            // <FieldInitializer>
            public readonly struct Measurement
            {
                public Measurement(double value)
                {
                    Value = value;
                }

                public Measurement(double value, string description)
                {
                    Value = value;
                    Description = description;
                }

                public Measurement(string description)
                {
                    Description = description;
                }
            
                public double Value { get; init; }
                public string Description { get; init; } = "Ordinary measurement";
            
                public override string ToString() => $"{Value} ({Description})";
            }

            public static void Main()
            {
                var m1 = new Measurement(5);
                Console.WriteLine(m1);  // output: 5 (Ordinary measurement)

                var m2 = new Measurement();
                Console.WriteLine(m2);  // output: 0 ()

                var m3 = default(Measurement);
                Console.WriteLine(m3);  // output: 0 ()
            }
            // </FieldInitializer>
        }
    }

    namespace with_expression
    {
        public static class Example
        {
            // <WithExpression>
            public readonly struct Coords
            {
                public Coords(double x, double y)
                {
                    X = x;
                    Y = y;
                }

                public double X { get; init; }
                public double Y { get; init; }

                public override string ToString() => $"({X}, {Y})";
            }

            public static void Main()
            {
                var p1 = new Coords(0, 0);
                Console.WriteLine(p1);  // output: (0, 0)

                var p2 = p1 with { X = 3 };
                Console.WriteLine(p2);  // output: (3, 0)

                var p3 = p1 with { X = 1, Y = 4 };
                Console.WriteLine(p3);  // output: (1, 4)
            }
            // </WithExpression>
        }
    }

    namespace InlineArrays
    {
        // <DeclareInlineArray>
        [System.Runtime.CompilerServices.InlineArray(10)]
        public struct CharBuffer
        {
            private char _firstElement;
        }
        // </DeclareInlineArray>

        // <DeclareInlineArrayWithPointer>
        [System.Runtime.CompilerServices.InlineArray(10)]
        public struct CharBufferWithPointer
        {
            private unsafe char* _pointerElement;    // CS9184
        }
        // </DeclareInlineArrayWithPointer>

        // <DeclareInlineArrayWithReferenceType>
        [System.Runtime.CompilerServices.InlineArray(10)]
        public struct CharBufferWithReferenceType
        {
            private string _referenceElement;
        }
        // </DeclareInlineArrayWithReferenceType>
    }
}

In .NET, examples of a ref struct are System.Span and System.ReadOnlySpan.

ref fields

You can declare a ref field in a ref struct, as the following example shows:

sample · csharpdocs/csharp/language-reference/builtin-types/snippets/shared/StructType.csSnippetRefField

csharp
namespace builtin_types
{
    public static class StructType
    {
        public static void Examples()
        {
            without_new.StructWithoutNew.Main();
            parameterless_constructor.Example.Main();
            field_initializer.Example.Main();
            with_expression.Example.Main();
        }

        // <SnippetStructExample>
        public struct Coords
        {
            public Coords(double x, double y)
            {
                X = x;
                Y = y;
            }

            public double X { get; }
            public double Y { get; }

            public override string ToString() => $"({X}, {Y})";
        }
        // </SnippetStructExample>
    }

    namespace without_new
    {
        // <SnippetWithoutNew>
        public static class StructWithoutNew
        {
            public struct Coords
            {
                public double x;
                public double y;
            }

            public static void Main()
            {
                Coords p;
                p.x = 3;
                p.y = 4;
                Console.WriteLine($"({p.x}, {p.y})");  // output: (3, 4)
            }
        }
        // </SnippetWithoutNew>
    }

    namespace readonly_struct
    {
        // <SnippetReadonlyStruct>
        public readonly struct Coords
        {
            public Coords(double x, double y)
            {
                X = x;
                Y = y;
            }

            public double X { get; init; }
            public double Y { get; init; }

            public override string ToString() => $"({X}, {Y})";
        }
        // </SnippetReadonlyStruct>
    }

    namespace readonly_members
    {
        public struct Example
        {
            // <SnippetReadonlyProperty>
            private int counter;
            public int Counter
            {
                readonly get => counter;
                set => counter = value;
            }
            // </SnippetReadonlyProperty>

            // <ReadonlyWithInit>
            public readonly double X { get; init; }
            // </ReadonlyWithInit>
            public readonly double Y { get; init; }

            // <SnippetReadonlyMethod>
            public readonly double Sum()
            {
                return X + Y;
            }
            // </SnippetReadonlyMethod>

            // <SnippetReadonlyOverride>
            public readonly override string ToString() => $"({X}, {Y})";
            // </SnippetReadonlyOverride>
        }
    }

    namespace ref_struct
    {
        // <SnippetRefStruct>
        public ref struct CustomRef
        {
            public bool IsValid;
            public Span<int> Inputs;
            public Span<int> Outputs;
        }
        // </SnippetRefStruct>

        // <SnippetReadonlyRef>
        public readonly ref struct ConversionRequest
        {
            public ConversionRequest(double rate, ReadOnlySpan<double> values)
            {
                Rate = rate;
                Values = values;
            }

            public double Rate { get; }
            public ReadOnlySpan<double> Values { get; }
        }
        // </SnippetReadonlyRef>

        // <SnippetRefField>
        public ref struct RefFieldExample
        {
            private ref int number;

            public int GetNumber()
            {
                if (System.Runtime.CompilerServices.Unsafe.IsNullRef(ref number))
                {
                    throw new InvalidOperationException("The number ref field is not initialized.");
                }

                return number;
            }
        }
        // </SnippetRefField>
    }

    namespace parameterless_constructor
    {
        public static class Example
        {
            // <ParameterlessConstructor>
            public readonly struct Measurement
            {
                public Measurement()
                {
                    Value = double.NaN;
                    Description = "Undefined";
                }

                public Measurement(double value, string description)
                {
                    Value = value;
                    Description = description;
                }
            
                public double Value { get; init; }
                public string Description { get; init; }
            
                public override string ToString() => $"{Value} ({Description})";
            }

            public static void Main()
            {
                var m1 = new Measurement();
                Console.WriteLine(m1);  // output: NaN (Undefined)

                var m2 = default(Measurement);
                Console.WriteLine(m2);  // output: 0 ()

                var ms = new Measurement[2];
                Console.WriteLine(string.Join(", ", ms));  // output: 0 (), 0 ()
            }
            // </ParameterlessConstructor>
        }
    }

    namespace field_initializer
    {
        public static class Example
        {
            // <FieldInitializer>
            public readonly struct Measurement
            {
                public Measurement(double value)
                {
                    Value = value;
                }

                public Measurement(double value, string description)
                {
                    Value = value;
                    Description = description;
                }

                public Measurement(string description)
                {
                    Description = description;
                }
            
                public double Value { get; init; }
                public string Description { get; init; } = "Ordinary measurement";
            
                public override string ToString() => $"{Value} ({Description})";
            }

            public static void Main()
            {
                var m1 = new Measurement(5);
                Console.WriteLine(m1);  // output: 5 (Ordinary measurement)

                var m2 = new Measurement();
                Console.WriteLine(m2);  // output: 0 ()

                var m3 = default(Measurement);
                Console.WriteLine(m3);  // output: 0 ()
            }
            // </FieldInitializer>
        }
    }

    namespace with_expression
    {
        public static class Example
        {
            // <WithExpression>
            public readonly struct Coords
            {
                public Coords(double x, double y)
                {
                    X = x;
                    Y = y;
                }

                public double X { get; init; }
                public double Y { get; init; }

                public override string ToString() => $"({X}, {Y})";
            }

            public static void Main()
            {
                var p1 = new Coords(0, 0);
                Console.WriteLine(p1);  // output: (0, 0)

                var p2 = p1 with { X = 3 };
                Console.WriteLine(p2);  // output: (3, 0)

                var p3 = p1 with { X = 1, Y = 4 };
                Console.WriteLine(p3);  // output: (1, 4)
            }
            // </WithExpression>
        }
    }

    namespace InlineArrays
    {
        // <DeclareInlineArray>
        [System.Runtime.CompilerServices.InlineArray(10)]
        public struct CharBuffer
        {
            private char _firstElement;
        }
        // </DeclareInlineArray>

        // <DeclareInlineArrayWithPointer>
        [System.Runtime.CompilerServices.InlineArray(10)]
        public struct CharBufferWithPointer
        {
            private unsafe char* _pointerElement;    // CS9184
        }
        // </DeclareInlineArrayWithPointer>

        // <DeclareInlineArrayWithReferenceType>
        [System.Runtime.CompilerServices.InlineArray(10)]
        public struct CharBufferWithReferenceType
        {
            private string _referenceElement;
        }
        // </DeclareInlineArrayWithReferenceType>
    }
}

A ref field can have the null value. Use the System.Runtime.CompilerServices.Unsafe.IsNullRef(@) method to determine if a ref field is null.

You can apply the readonly modifier to a ref field in the following ways:

  • readonly ref: You can ref reassign this field by using the = ref operator only inside a constructor or an init accessor. You can assign a value with the = operator at any point allowed by the field access modifier.
  • ref readonly: At any point, you can't assign a value with the = operator to this field. However, you can ref reassign the field by using the = ref operator.
  • readonly ref readonly: You can only ref reassign this field in a constructor or an init accessor. At any point, you can't assign a value to the field.

The compiler ensures that a reference stored in a ref field doesn't outlive its referent.

The ref fields feature enables a safe implementation of types like System.Span:

csharp
public readonly ref struct Span<T>
{
    internal readonly ref T _reference;
    private readonly int _length;

    // Omitted for brevity...
}

The Span<T> type stores a reference through which it accesses the contiguous elements in memory. By using a reference, a Span<T> instance avoids copying the storage it refers to.

The disposable pattern

You can define a disposable ref struct. To do that, ensure that a ref struct fits the disposable pattern. That is, it has an instance Dispose method that's accessible, parameterless, and has a void return type. You can use the using statement or declaration with an instance of a disposable ref struct.

Beginning with C# 13, you can also implement the System.IDisposable on ref struct types. However, overload resolution prefers the disposable pattern to the interface method. The compiler resolves to an IDisposable.Dispose method only when a suitable Dispose method isn't found.

Restrictions for ref struct types that implement an interface

These restrictions ensure that a ref struct type that implements an interface follows the necessary ref safety rules.

  • You can't convert a ref struct to an instance of an interface it implements. This restriction includes the implicit conversion when you use a ref struct type as an argument and the parameter is an interface type. The conversion results in a boxing conversion, which violates ref safety. A ref struct can declare methods as explicit interface declarations. However, you can access those methods only from generic methods where the type parameter allows ref struct types.
  • A ref struct that implements an interface must implement all instance interface members. The ref struct must implement instance members even when the interface includes a default implementation.

The compiler enforces these restrictions. If you write ref struct types that implement interfaces, each new update might include new default interface members. Until you provide an implementation for any new instance methods, your application doesn't compile. You can't provide a specific implementation for a static interface method with a default implementation.

C# language specification

For more information, see the following sections of the C# language specification:

For more information about ref fields, see Ref fields in the C# language specification.

See also