Dela via


Strukturtyper (C#-referens)

En strukturtyp (eller structtyp) är en värdetyp som kan kapsla in data och relaterade funktioner. Du använder 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 kopieras variabelvärden vid tilldelning, skickar ett argument till en metod och returnerar ett metodresultat. För variabler av strukturtyp kopieras 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, 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

Du använder readonly 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:

Det garanterar att ingen medlem i en readonly struct ändrar structens tillstånd. Det innebär att andra instansmedlemmar utom konstruktorer implicit readonlyär .

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

Du kan också använda readonly 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 medlem kan dock readonly anropa en icke-medlemreadonly . I så fall skapar kompilatorn en kopia av strukturinstansen och anropar den som intereadonly är medlem på 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 modifieraren för readonly 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 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

Från och med C# 10 kan du använda with uttrycket för att skapa en kopia av en instans av strukturtyp med de angivna egenskaperna och fälten ändrade. Du använder syntaxen för objektinitieraren 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

Från och med C# 10 kan du definiera poststrukturtyper. Posttyper ger inbyggda funktioner för att kapsla in data. Du kan definiera både record struct och readonly record struct typer. En post struct kan inte vara en ref struct. Mer information och exempel finns i Poster.

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 infogad 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 struct.

I de flesta fall kan en infogad matris nås som en matris, både för att läsa och skriva värden. Dessutom kan du 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. Det 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 typerna lagrar sina data direkt. Värdet default för en struct har definitivt tilldelat 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:

  • Du kan lägga till fältinitierare i valfri fält- eller automatiskt implementerad egenskap.
  • Du kan initiera alla fält eller automatiska egenskaper i konstruktorns brödtext.

Från och med C# 11 lägger kompilatorn till kod i konstruktorn som initierar dessa fält till standardvärdet om du inte initierar alla fält i en struct. Kompilatorn utför sin vanliga definitiva tilldelningsanalys. Alla fält som används innan de tilldelas eller definitivt inte tilldelas när konstruktorn har slutfört körningen tilldelas sina standardvärden innan konstruktorns brödtext körs. Om this används innan alla fält tilldelas initieras structen till standardvärdet innan konstruktorns brödtext körs.

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 konstruktor har en public parameterlös 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. Mer information finns i 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 brödtexten struct , i alla medlemsdeklarationer för den structen.

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

När det gäller de inbyggda värdetyperna anvä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 vissa undantag och vissa undantag som har tagits bort i senare versioner:

  • 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.
  • Före C# 11 måste en konstruktor av en strukturtyp initiera alla instansfält av typen.

Skicka variabler av strukturtyp efter 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. refAnvänd parametermodifierarna , out, ineller ref readonly method för att ange att ett argument måste skickas med referens. Använd referensreturer för att returnera ett metodresultat efter referens. Mer information finns i Undvik allokeringar.

struct-villkor

Du använder också nyckelordet struct i villkoret struct 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 (förutom ref struct typer) finns det boxnings- och avboxningskonverteringar till och från typerna System.ValueType och System.Object . Det finns också boxnings- och avboxningskonverteringar 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