Dela via


Strukturtyper (C#-referens)

En strukturtyp (eller structtyp) är en värdetyp som kan kapsla in data och relaterade funktioner.

C#-språkreferensen dokumenterar den senaste versionen av C#-språket. Den innehåller även inledande dokumentation för funktioner i offentliga förhandsversioner för den kommande språkversionen.

Dokumentationen identifierar alla funktioner som först introducerades i de tre senaste versionerna av språket eller i aktuella offentliga förhandsversioner.

Tips/Råd

Information om när en funktion först introducerades i C# finns i artikeln om språkversionshistoriken för C#.

Använd nyckelordet struct för att definiera en strukturtyp:

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

Information om ref struct och readonly ref struct typer finns i artikeln referensstrukturtyper .

Strukturtyper har värdesemantik. En variabel av en strukturtyp innehåller alltså en instans av typen. Som standard kopierar systemet variabelvärden vid tilldelning, när ett argument skickas till en metod och när ett metodresultat returneras. För variabler av strukturtyp kopierar systemet en instans av typen. Mer information finns i Värdetyper.

Vanligtvis använder du strukturtyper för att utforma små datacentrerade typer som ger lite eller inget beteende. .NET använder till exempel strukturtyper för att representera ett tal (både heltal och verkligt), ett booleskt värde, ett Unicode-tecken och en tidsinstans. Om du fokuserar på beteendet för en typ kan du överväga att definiera en klass. Klasstyper har referenssemantik. En variabel av en klasstyp innehåller alltså en referens till en instans av typen, inte själva instansen.

Eftersom strukturtyper har värdesemantik rekommenderar vi att du definierar oföränderliga strukturtyper.

readonly Struct

readonly Använd modifieraren för att deklarera att en strukturtyp är oföränderlig. Alla datamedlemmar i en readonly struct måste vara skrivskyddade enligt följande:

Den här regeln garanterar att ingen medlem i en readonly struct ändrar structens tillstånd. Alla andra instansmedlemmar utom konstruktorer är implicit readonly.

Kommentar

I en readonly struct kan en datamedlem av en föränderlig referenstyp fortfarande mutera sitt eget tillstånd. Du kan till exempel inte ersätta en List<T> instans, men du kan lägga till nya element i den.

Följande kod definierar en readonly struct med init-only-egenskapsuppsättningar:

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 instansmedlemmar

readonly Använd modifieraren för att deklarera att en instansmedlem inte ändrar tillståndet för en struct. Om du inte kan deklarera hela strukturtypen som readonlyanvänder du readonly modifieraren för att markera de instansmedlemmar som inte ändrar struct-tillståndet.

I en readonly instansmedlem kan du inte tilldela till strukturens instansfält. En readonly medlem kan dock anropa en icke-readonly medlem. I så fall skapar kompilatorn en kopia av strukturinstansen och anropar medlemmen som inte är readonly på den kopian. Därför ändras inte den ursprungliga strukturinstansen.

Vanligtvis tillämpar du modifieraren på readonly följande typer av instansmedlemmar:

  • Metoder:

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

    Du kan också tillämpa modifieraren på readonly metoder som åsidosätter metoder som deklareras i System.Object:

    public readonly override string ToString() => $"({X}, {Y})";
    
  • Egenskaper och indexerare:

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

    Om du behöver tillämpa readonly modifieraren på båda åtkomstgivarna för en egenskap eller indexerare använder du den i deklarationen för egenskapen eller indexeraren.

    Kommentar

    Kompilatorn deklarerar en get åtkomst till en automatiskt implementerad egenskap som readonly, oavsett förekomsten av readonly modifieraren i en egenskapsdeklaration.

    Du kan använda readonly-modifieraren för en egenskap eller indexerare med en init-accessor:

    public readonly double X { get; init; }
    

Du kan använda readonly modifieraren för statiska fält av en strukturtyp, men inte för andra statiska medlemmar, till exempel egenskaper eller metoder.

Kompilatorn kan använda readonly modifieraren för prestandaoptimering. Mer information finns i Undvika allokeringar.

Icke-förstörande mutation

Använd uttrycketwith för att skapa en kopia av en instans av strukturtyp med de angivna egenskaperna och fälten ändrade. Använd syntaxen för initiering av objekt för att ange vilka medlemmar som ska ändras och deras nya värden, som följande exempel visar:

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 Struct

Du kan definiera datapoststrukturtyper. Posttyper ger inbyggd funktionalitet för att kapsla data. Du kan definiera både record struct och readonly record struct typer. En record struct kan inte vara en ref struct. Mer information och exempel finns i Register.

Infogade matriser

Från och med C# 12 kan du deklarera infogade matriser som en struct typ:

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

En infogad matris är en struktur som innehåller ett sammanhängande block med N-element av samma typ. Det är en motsvarighet till säker kod för den fasta buffertdeklarationen som endast är tillgänglig i osäker kod. En inline-matris är en struct med följande egenskaper:

  • Den innehåller ett enda fält.
  • Struct anger inte någon explicit layout.

Dessutom validerar System.Runtime.CompilerServices.InlineArrayAttribute kompilatorn attributet:

  • Längden måste vara större än noll (> 0).
  • Måltypen måste vara en struktur.

I de flesta fall kan du komma åt en infogad matris som en matris, både för att läsa och skriva värden. Du kan också använda intervall - och indexoperatorerna .

Det finns minimala begränsningar för typen av det enskilda fältet i en infogad matris. Det kan inte vara en pekartyp:

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

Men det kan vara vilken referenstyp som helst eller vilken värdetyp som helst:

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

Du kan använda infogade matriser med nästan vilken C#-datastruktur som helst.

Infogade matriser är en avancerad språkfunktion. De är avsedda för scenarier med höga prestanda där ett sammanhängande elementblock är snabbare än andra alternativa datastrukturer. Du kan lära dig mer om infogade matriser från funktionsspecifikationen.

Struct-initiering och standardvärden

En variabel av en struct typ innehåller direkt data för den struct. Den här direktdatalagringen skapar en skillnad mellan en ennitialiserad struct, som har sitt standardvärde och en initierad struct, som lagrar värden som anges genom att konstruera den. Tänk till exempel på följande kod:

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

Som föregående exempel visar ignorerar standardvärdeuttrycket en parameterlös konstruktor och genererar standardvärdet för strukturtypen. Instansiering av matriser av strukturtyp ignorerar också en parameterlös konstruktor och skapar en matris som fylls i med standardvärdena för en strukturtyp.

Den vanligaste situationen där du ser standardvärden finns i matriser eller i andra samlingar där intern lagring innehåller block med variabler. I följande exempel skapas en matris med 30 TemperatureRange strukturer som var och en har standardvärdet:

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

Alla en structs medlemsfält måste definitivt tilldelas när de skapas eftersom struct typer lagrar sina data direkt. Värdet default för en struct tilldelar definitivt alla fält till 0. Alla fält måste definitivt tilldelas när en konstruktor anropas. Du initierar fält med hjälp av följande mekanismer:

  • Lägg till fältinitierare i valfri fält- eller automatiskt implementerad egenskap.
  • Initiera fält eller automatiska egenskaper i konstruktorns brödtext.

Om du inte initierar alla fält i en struct lägger kompilatorn till kod till konstruktorn som initierar dessa fält till standardvärdet. En struct som tilldelats dess default-värde initieras till 0-bitarsmönstret. En struct initierad med new initieras till nollbitmönstret, därefter körs eventuella fältinitierare och en konstruktor.

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

Varje struct har en parameterlös public konstruktor. Om du skriver en parameterlös konstruktor måste den vara offentlig. Om en struct deklarerar några fältinitierare måste den uttryckligen deklarera en konstruktor. Konstruktorn behöver inte vara parameterlös. Om en struct deklarerar en fältinitierare men inga konstruktorer rapporterar kompilatorn ett fel. Alla explicit deklarerade konstruktorer (med parametrar eller parameterlösa) kör alla fältinitierare för den structen. Alla fält utan fältinitierare eller tilldelning i en konstruktor är inställda på standardvärdet. För mer information, se funktionsförslaget för parameterlösa structkonstruktorer.

Från och med C# 12 struct kan typer definiera en primär konstruktor som en del av deklarationen. Primära konstruktorer ger en koncis syntax för konstruktorparametrar som kan användas i hela kroppsdelen av struct, i alla medlemsdeklarationer för den strukturen.

Om alla instansfält av en strukturtyp är tillgängliga kan du även instansiera det utan operatorn new . I så fall måste du initiera alla instansfält innan den första användningen av instansen. I följande exempel visas hur du gör det:

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 de inbyggda värdetypernaanvänder du motsvarande literaler för att ange ett värde av typen.

Begränsningar med designen av en strukturtyp

Structs har de flesta funktionerna i en klasstyp . Det finns några undantag:

  • En strukturtyp kan inte ärva från en annan klass eller strukturtyp och det kan inte vara basen för en klass. En strukturtyp kan dock implementera gränssnitt.
  • Du kan inte deklarera en finalator inom en strukturtyp.
  • En konstruktor av en strukturtyp måste initiera alla instansfält av typen.

Skicka variabler av strukturtyp via referens

När du skickar en variabel av strukturtyp till en metod som ett argument eller returnerar ett strukturtypvärde från en metod kopieras hela instansen av en strukturtyp. Genomströmningsvärde kan påverka kodens prestanda i scenarier med höga prestanda som omfattar stora strukturtyper. Du kan undvika värdekopiering genom att skicka en variabel av strukturtyp med referens. Använd parametermodifierarna ref, out, in eller ref readonly för method för att ange att ett argument måste skickas genom referens. Använd referensreturer för att returnera ett metodresultat efter referens. Mer information finns i Undvik allokeringar.

struct begränsning

Använd nyckelordet struct i villkoretstruct för att ange att en typparameter är en värdetyp som inte kan null-värdet. Både struktur- och uppräkningstyper uppfyller villkoret struct .

Omvandlingar

För alla strukturtyper (utom ref struct typer) finns konverteringar av boxning och avboxning till och från typerna System.ValueType och System.Object . Konverteringar för boxning och avboxning finns också mellan en strukturtyp och alla gränssnitt som implementeras.

Språkspecifikation för C#

Mer information finns i avsnittet Structs i C#-språkspecifikationen.

Mer information om struct funktioner finns i följande kommentarer om funktionsförslag:

Se även