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. Init-only-Setter sind nur verfügbar von C# Version 9 aufwärts.

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 die readonly Modifikator an eine Eigenschaft oder einen Indexer mit einer init Zugriff:

    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

Sie können die with Ausdruck um eine Kopie einer strukturartigen Instanz zu erzeugen, bei der die angegebenen Eigenschaften und Felder geändert wurden. 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

Sie können 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
}

Dabei kann es sich jedoch um einen beliebigen Bezugstyp oder einen beliebigen Werttyp handeln:

[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 Mitgliedsfelder einer Struktur müssen eindeutig zugeordnet bei der Erstellung, weil struct Typen ihre Daten direkt speichern. Die default Wert einer Struktur eindeutig zugeordnet alle Felder auf 0. 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. Eine Struktur, die ihrer default Wert wird mit dem 0-Bit-Muster initialisiert. Eine Zeichenkette, initialisiert mit new wird mit dem 0-Bit-Muster initialisiert, gefolgt von der Ausführung aller Feldinitialisierer und eines Konstruktors.

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. Primäre Konstruktoren bieten eine prägnante Syntax für Konstruktorparameter, die in der gesamten Software verwendet werden können. struct Körper, in jeder Member-Deklaration für diese Struktur.

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

Für die eingebaute Wertetypen, verwenden Sie die entsprechenden Literale, um einen Wert des Typs anzugeben.

Einschränkungen beim Entwerfen eines Strukturtyps

Strukturen verfügen über die meisten Funktionen eines Klassentyps. Es gibt einige Ausnahmen:

  • 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.

Ü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