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

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 nutzen, wird die Definition von unveränderlichen Strukturtypen empfohlen.

readonly-Struktur

Ab C# 7.2 können Sie 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, auch automatisch implementierte, müssen schreibgeschützt sein. In C# 9.0 und höher kann eine Eigenschaft einen init-Accessor aufweisen.

Auf diese Weise ist garantiert, dass kein Member einer readonly-Struktur den Status der Struktur ändert. In C# 8.0 und höher bedeutet dies, 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

Ab C# 8.0 können Sie 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. Daher 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 davon, ob der readonly-Modifizierer in einer Eigenschaftsdeklaration vorhanden ist.

    In C# 9.0 und höher können Sie 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 keine anderen statischen Elemente, z. B. Eigenschaften oder Methoden.

Der Compiler kann den readonly-Modifizierer für Leistungsoptimierungen verwenden. Weitere Informationen finden Sie unter Schreiben von sicherem und effizientem C#-Code.

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 Funktionalität für die Kapselung von Daten. Sie können beide record structreadonly record struct und Typen definieren. Eine Datensatzstruktur kann keine Struktur seinref. Weitere Informationen und Beispiele finden Sie unter Datensätze.

Struct Initialisierung und Standardwerte

Eine Variable eines struct Typs enthält die Daten für diesen structTyp direkt. Dadurch wird eine Unterscheidung zwischen einem nicht initialisierten structWert erstellt, der seinen Standardwert und einen initialisierten structWert aufweist, der Werte speichert, die durch erstellen festgelegt werden. Betrachten Sie z. B. 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 im vorherigen Beispiel gezeigt, 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 in Arrays oder in anderen Sammlungen angezeigt werden, in denen interner 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 definitiv zugewiesen werden, wenn sie erstellt werden, da struct Typen ihre Daten direkt speichern. Der default Wert einer Struktur hat definitiv allen Feldern 0 zugewiesen . Alle Felder müssen definitiv zugewiesen werden, wenn ein Konstruktor aufgerufen wird. Sie initialisieren Felder mithilfe der folgenden Mechanismen:

  • Sie können Feld-Initializer zu einer beliebigen Feld- oder automatisch implementierten Eigenschaft hinzufügen.
  • Sie können alle Felder oder automatischen Eigenschaften im Textkörper des Konstruktors initialisieren.

Ab C# 11 initialisieren Sie nicht alle Felder in einer Struktur, fügt der Compiler dem Konstruktor Code hinzu, der diese Felder zum Standardwert initialisiert. Der Compiler führt seine übliche eindeutige Zuordnungsanalyse aus. Alle Felder, auf die vor dem Zugewiesenen zugegriffen werden oder nicht definitiv zugewiesen werden, wenn der Konstruktor ausgeführt wird, werden ihren Standardwerten zugewiesen, bevor der Konstruktortext ausgeführt wird. Wenn this vor allen Feldern zugegriffen wird, wird die Struktur an den Standardwert initialisiert, bevor der Konstruktortext 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 ()
}

Jeder struct hat einen public parameterlosen Konstruktor. Wenn Sie einen parameterlosen Konstruktor schreiben, muss es öffentlich sein. Wenn Sie keinen öffentlichen parameterlosen Konstruktor schreiben, generiert der Compiler eine. Der compiler generierte parameterlose Konstruktor führt alle Feld initialisiert aus, und erzeugt den Standardwert für alle anderen Felder. Wenn Sie feld initializer deklarieren, müssen Sie einen expliziten Konstruktor deklarieren. Der eine explizite Konstruktor kann der parameterlose Konstruktor sein. Es kann einen leeren Textkörper haben. Weitere Informationen finden Sie im Featurevorschlagshinweis für parameterlose Strukturkonstruktoren.

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

Structs 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 ein Konstruktor eines Strukturtyps alle Instanzfelder des Typs initialisieren.
  • Vor C# 10 können Sie keinen Parameterlosen Konstruktor deklarieren.
  • Vor C# 10 können Sie kein Instanzfeld oder eine Eigenschaft in der Deklaration 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. Der Wert kann sich auf die Leistung Ihres Codes in Hochleistungsszenarien auswirken, die große Strukturtypen umfassen. Sie können das Kopieren von Werten vermeiden, indem Sie eine Strukturtypvariable als Verweis übergeben. Verwenden Sie die Methodenparametermodifizierer ref, out oder in, 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 Schreiben von sicherem und effizientem C#-Code.

ref-Struktur

Ab C# 7.2 können Sie bei der Deklaration eines Strukturtyps den Modifizierer ref verwenden. Instanzen eines ref-Strukturtyps werden auf dem Stapel zugeordnet und können nicht in den verwalteten Heap überwechseln. Der Compiler grenzt die Nutzung von ref-Strukturtypen wie folgt ein, um dies sicherzustellen:

  • Eine ref-Struktur kann nicht der Elementtyp eines Arrays sein.
  • Eine ref-Struktur kann kein deklarierter Typ eines Felds einer Klasse oder einer Struktur sein, bei der es sich um keine ref-Struktur handelt.
  • Eine ref-Struktur kann keine Schnittstellen implementieren.
  • Für eine ref-Struktur kann kein Boxing in System.ValueType oder System.Object durchgeführt werden.
  • Eine ref-Struktur kann kein Typargument sein.
  • Eine ref-Strukturvariable kann nicht von einem Lambdaausdruck oder einer lokalen Funktion erfasst werden.
  • Eine ref-Strukturvariable kann nicht in einer async-Methode verwendet werden. Sie können jedoch Strukturvariablen in synchronen Methoden verwenden ref , z. B. in Methoden, die zurückgegeben oder zurückgegeben Task werden Task<TResult>.
  • Eine ref-Strukturvariable kann nicht in Iteratoren verwendet werden.

Ab C# 8.0 können Sie eine verwerfbare ref-Struktur definieren. Stellen Sie dazu sicher, dass eine ref-Struktur dem verwerfbaren Muster entspricht. Das heißt, sie umfasst eine Instanz- oder Erweiterungsmethode Dispose, auf die zugegriffen werden kann, die parameterlos ist und einen Rückgabetyp void aufweist.

In der Regel definieren Sie einen ref-Strukturtyp, wenn Sie einen Typ benötigen, der auch Datenmember von ref-Strukturtypen enthält:

public ref struct CustomRef
{
    public bool IsValid;
    public Span<int> Inputs;
    public Span<int> Outputs;
}

Kombinieren Sie die readonly- und ref-Modifizierer in der Typdeklaration (der readonly-Modifizierer muss vor dem ref-Modifizierer stehen), um eine ref-Struktur als readonly zu deklarieren:

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

In .NET sind System.Span<T> und System.ReadOnlySpan<T> Beispiele für eine ref-Struktur.

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 refstruct-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 in C# 7.2 und höher eingeführten Features finden Sie in den folgenden Featurevorschlägen:

Siehe auch