Freigeben über


Strukturtypen (C#-Referenz)

Ein Strukturtyp (oder struct type) ist ein Werttyp, der Daten und zugehörige Funktionen kapseln kann. Verwenden Sie das struct-Schlüsselwort, um einen Strukturtyp zu definieren:

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})";
}

Informationen zu ref struct- und readonly ref struct-Typen finden Sie im Artikel Verweisstrukturtypen.

Strukturtypen verfügen über eine Wertsemantik. Das heißt, eine Variable eines Strukturtyps enthält eine Instanz des Typs. Standardmäßig werden die Variablenwerte bei der Zuweisung kopiert, dabei handelt es sich um die Übergabe eines Arguments an eine Methode oder die Rückgabe eines Methodenergebnisses. Für Strukturtypvariablen wird eine Instanz des Typs kopiert. Weitere Informationen finden Sie unter Werttypen.

In der Regel werden Strukturtypen zum Entwerfen kleiner datenorientierter Typen verwendet, die wenig oder gar kein Verhalten bereitstellen. Beispielsweise verwendet .NET Strukturtypen, um Zahlen (sowohl Integer als auch reelle Zahlen), boolesche Werte, Unicode-Zeichen und Zeitinstanzen darzustellen. Wenn Sie das Verhalten eines Typs verwenden möchten, sollten Sie eine Klasse definieren. Klassentypen verfügen über Verweissemantik. Das heißt, eine Variable eines Klassentyps enthält einen Verweis auf eine Instanz des Typs, nicht die Instanz selbst.

Da Strukturtypen eine Wertsemantik aufweisen, wird die Definition von unveränderlichen Strukturtypen empfohlen.

readonly-Struktur

Sie können mit dem readonly-Modifizierer einen Strukturtyp als unveränderlich deklarieren. Alle Datenmember einer readonly-Struktur müssen als schreibgeschützt gekennzeichnet sein:

  • Alle Felddeklarationen müssen den readonly-Modifizierer aufweisen.
  • Alle Eigenschaften, einschließlich automatisch implementierter, müssen schreibgeschützt oder init nur schreibgeschützt sein. Beachten Sie, dass Init-Only-Setter nur ab C# Version 9 und höher verfügbar sind.

Auf diese Weise ist garantiert, dass kein Member einer readonly-Struktur den Status der Struktur ändert. Das bedeutet, dass andere Instanzmember mit Ausnahme von Konstruktoren implizit readonly werden.

Hinweis

In einer readonly-Struktur kann ein Datenmember eines änderbaren Verweistyps weiterhin den eigenen Status ändern. Beispielsweise können Sie eine List<T>-Instanz nicht ersetzen, aber neue Elemente zur Instanz hinzufügen.

Der folgende Code definiert eine readonly-Struktur mit Nur-init-Eigenschaftensettern, die in C# 9.0 und höher verfügbar sind:

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})";
}

readonly-Instanzmember

Sie können auch den readonly-Modifizierer verwenden, um zu deklarieren, dass ein Instanzmember den Zustand einer Struktur nicht ändert. Wenn Sie nicht den gesamten Strukturtyp als readonly deklarieren können, verwenden Sie den readonly-Modifizierer, um die Instanzmember zu markieren, die den Zustand der Struktur nicht ändern.

Innerhalb eines readonly-Instanzmembers können Sie den Instanzfeldern einer Struktur nichts zuweisen. Ein readonly-Member kann jedoch ein Nicht-readonly-Member aufrufen. In diesem Fall erstellt der Compiler eine Kopie der Strukturinstanz und ruft den Nicht-readonly-Member in dieser Kopie auf. Folglich wird die ursprüngliche Strukturinstanz nicht geändert.

In der Regel wenden Sie den readonly-Modifizierer auf die folgenden Arten von Instanzmembern an:

  • Methoden:

    public readonly double Sum()
    {
        return X + Y;
    }
    

    Sie können den readonly-Modifizierer auch auf Methoden anwenden, die in System.Object deklarierte Methoden überschreiben:

    public readonly override string ToString() => $"({X}, {Y})";
    
  • Eigenschaften und Indexer:

    private int counter;
    public int Counter
    {
        readonly get => counter;
        set => counter = value;
    }
    

    Wenn Sie den readonly-Modifizierer auf die Accessoren sowohl einer Eigenschaft als auch eines Indexers anwenden müssen, wenden Sie ihn in der Deklaration der Eigenschaft bzw. des Indexers an.

    Hinweis

    Der Compiler deklariert einen get Accessor einer automatisch implementierten Eigenschaft als readonly, unabhängig vom Vorhandensein des readonly Modifizierers in einer Eigenschaftsdeklaration.

    Sie können den readonly-Modifizierer auf eine Eigenschaft oder einen Indexer mit einem init-Accessor anwenden:

    public readonly double X { get; init; }
    

Sie können den readonly-Modifizierer auf statische Felder eines Strukturtyps anwenden, aber nicht auf andere statische Member, z. B. Eigenschaften oder Methoden.

Der Compiler kann den readonly-Modifizierer für Leistungsoptimierungen verwenden. Weitere Informationen finden Sie unter Vermeiden von Reservierungen.

Nichtdestruktive Mutation

Ab C# 10 können Sie den Ausdruck with verwenden, um eine Kopie einer Strukturtypinstanz mit den angegebenen Eigenschaften und geänderten Feldern zu erstellen. Sie verwenden die Objektinitialisierersyntax, um anzugeben, welche Member bearbeitet und welche neuen Werte dazu verwendet werden sollen, wie im folgenden Beispiel gezeigt:

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)
}

record-Struktur

Ab C# 10 können Sie Datensatzstrukturtypen definieren. Datensatztypen bieten integrierte Funktionen zum Kapseln von Daten. Sie können sowohl record struct- als auch readonly record struct-Typen definieren. Eine Datensatzstruktur kann kein ref struct sein. Weitere Informationen und Beispiele finden Sie unter Datensätze.

Inlinearrays

Ab C# 12 können Sie Inlinearrays als Typ struct deklarieren:

[System.Runtime.CompilerServices.InlineArray(10)]
public struct CharBuffer
{
    private char _firstElement;
}

Ein Inlinearray ist eine Struktur, die einen zusammenhängenden Block von N-Elementen desselben Typs enthält. Es ist ein Äquivalent der Deklaration eines festen Puffers, die nur in unsicherem Code verfügbar ist. Ein Inlinearray ist ein struct mit den folgenden Merkmalen:

  • Es enthält ein einzelnes Feld.
  • Die Struktur gibt kein explizites Layout an.

Darüber hinaus überprüft der Compiler das Attribut System.Runtime.CompilerServices.InlineArrayAttribute:

  • Die Länge muss größer als Null (> 0) sein.
  • Der Zieltyp muss eine Struktur sein.

In den meisten Fällen kann auf ein Inlinearray wie ein Array zugegriffen werden, um Werte zu lesen und zu schreiben. Darüber hinaus können Sie die Operatoren Bereich und Index verwenden.

Es gibt minimale Einschränkungen für den Typ des einzelnen Felds eines Inlinearrays. Es kann kein Zeigertyp sein:

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

es kann jedoch ein beliebiger Bezugstyp oder ein beliebiger Werttyp sein:

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

Sie können Inlinearrays mit fast jeder C#-Datenstruktur verwenden.

Inlinearrays sind ein erweitertes Sprachfeature. Sie sind für Hochleistungsszenarien vorgesehen, in denen ein inline zusammenhängender Block von Elementen schneller ist als andere alternative Datenstrukturen. Weitere Informationen zu Inlinearrays finden Sie im Feature-speclet

Strukturinitialisierung und Standardwerte

Eine Variable eines struct-Typs enthält direkt die Daten für die betreffende struct. Dadurch wird unterschieden zwischen einer nicht initialisierten struct, die einen Standardwert hat, und einer initialisierten struct, in der Werte gespeichert werden, die durch die Erstellung festgelegt wurden. Betrachten Sie beispielsweise den folgenden Code:

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 ()
}

Wie das obige Beispiel zeigt, ignoriert der Standardwertausdruck einen parameterlosen Konstruktor und erzeugt den Standardwert des Strukturtyps. Bei der Instanziierung des Strukturtyparrays wird ein parameterloser Konstruktor ebenfalls ignoriert und ein Array erzeugt, das mit den Standardwerten eines Strukturtyps aufgefüllt wird.

Die häufigste Situation, in der Standardwerte verwendet werden, sind Arrays oder andere Auflistungen, in denen der interne Speicher Blöcke von Variablen enthält. Im folgenden Beispiel wird ein Array von 30 TemperatureRange-Strukturen erstellt, die jeweils den Standardwert aufweisen:

// All elements have default values of 0:
TemperatureRange[] lastMonth = new TemperatureRange[30];

Alle Memberfelder einer Struktur müssen beim Erstellen definitiv zugewiesen werden, da struct-Typen ihre Daten direkt speichern. Der default-Wert einer Struktur hat allen Feldern definitiv 0 zugewiesen. Alle Felder müssen definitiv zugewiesen werden, wenn ein Konstruktor aufgerufen wird. Sie initialisieren Felder mit den folgenden Mechanismen:

  • Sie können Feldinitialisierer zu jedem Feld oder jeder automatisch implementierten Eigenschaft hinzufügen.
  • Sie können alle Felder oder automatischen Eigenschaften im Textkörper des Konstruktors initialisieren.

Wenn Sie ab C# 11 nicht alle Felder in einer Struktur initialisieren, fügt der Compiler dem Konstruktor Code hinzu, der diese Felder mit dem Standardwert initialisiert. Der Compiler führt die übliche definite Zuweisungsanalyse durch. Allen Feldern, auf die vor der Zuweisung zugegriffen wird oder die nicht definitiv zugewiesen werden, wenn die Ausführung des Konstruktors beendet wurde, werden ihre Standardwerte zugewiesen, bevor der Konstruktorcode ausgeführt wird. Bei einem Zugriff auf this, bevor alle Felder zugewiesen wurden, wird die Struktur mit dem Standardwert initialisiert, bevor der Konstruktorcode ausgeführt wird.

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 ()
}

Jede struct verfügt über einen parameterlosen public-Konstruktor. Wenn Sie einen parameterlosen Konstruktor schreiben, muss dieser öffentlich sein. Wenn eine Struktur Feldinitialisierer deklariert, muss sie explizit einen Konstruktor deklarieren. Der betreffende Konstruktor muss nicht parameterlos sein. Wenn eine Struktur einen Feldinitialisierer, aber keine Konstruktoren deklariert, meldet der Compiler einen Fehler. Jeder explizit deklarierte Konstruktor (mit oder ohne Parameter) führt alle Feldinitialisierer für die betreffende Struktur aus. Alle Felder ohne Feldinitialisierer oder Zuweisung in einem Konstruktor werden auf den Standardwert festgelegt. Weitere Informationen finden Sie im Featurevorschlagshinweis für parameterlose Strukturkonstruktoren.

Ab C# 12 können struct-Typen einen primären Konstruktor als Teil der Deklaration definieren. Die primären Konstruktoren sorgen für eine präzise Syntax für Konstruktorparameter, die im gesamten struct-Text in jeder Memberdeklaration für diese Struktur verwendet werden kann.

Wenn alle Instanzfelder eines Strukturtyps zugänglich sind, können Sie ihn auch ohne den new-Operator instanziieren. In diesem Fall müssen Sie alle Instanzfelder vor der ersten Verwendung der Instanz initialisieren. Das folgende Beispiel zeigt, wie Sie dabei vorgehen müssen:

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)
    }
}

Verwenden Sie für integrierte Werttypen die entsprechenden Literale, um den Wert des Typs festzulegen.

Einschränkungen beim Entwerfen eines Strukturtyps

Strukturen verfügen über die meisten Funktionen eines Klassentyps. Es gibt einige Ausnahmen und einige Ausnahmen, die in neueren Versionen entfernt wurden:

  • Ein Strukturtyp kann nicht von einer anderen Klasse oder einem anderen Strukturtyp erben, und er kann nicht die Basis einer Klasse sein. Allerdings kann ein Strukturtyp Schnittstellen implementieren.
  • Innerhalb eines Strukturtyps können Sie keinen Finalizer deklarieren.
  • Vor C# 11 muss der Konstruktor einer Strukturtyps alle Instanzfelder des Typs initialisieren.

Übergeben von Strukturtypvariablen als Verweis

Wenn Sie eine Strukturtypvariable als Argument an eine Methode übergeben oder einen Strukturtypvariable einer Methode zurückgeben, wird die gesamte Instanz des Strukturtyps kopiert. Die Wertübergabe nach Wert kann sich in Hochleistungsszenarios mit großen Strukturtypen auf die Leistung Ihres Codes auswirken. Sie können das Kopieren von Werten vermeiden, indem Sie eine Strukturtypvariable als Verweis übergeben. Verwenden Sie die Methodenparametermodifizierer ref, out, in oder ref readonly um anzugeben, dass ein Argument als Verweis übergeben werden muss. Verwenden Sie ref returns, um ein Methodenergebnis als Verweis zurückzugeben. Weitere Informationen finden Sie unter Vermeiden von Reservierungen.

struct-Einschränkung

Sie können auch das struct-Schlüsselwort in der struct-Einschränkung verwenden, um anzugeben, dass ein Typparameter ein Non-Nullable-Werttyp ist. Sowohl Struktur- als auch Enumerationstypen erfüllen die struct-Einschränkung.

Konvertierungen

Für alle Strukturtypen (außer ref struct-Typen) gibt es Boxing- und Unboxing-Umwandlungen in und aus den Typen System.ValueType und System.Object. Außerdem gibt es Boxing- und Unboxing-Umwandlungen zwischen einem Strukturtyp und der Schnittstelle, die ihn implementiert.

C#-Sprachspezifikation

Weitere Informationen finden Sie im Abschnitt Strukturen der C#-Sprachspezifikation.

Weitere Informationen zu struct-Features finden Sie in den folgenden Featurevorschlägen:

Weitere Informationen