Structuurtypen (C#-verwijzing)

Een structuurtype (of structtype) is een waardetype dat gegevens en gerelateerde functionaliteit kan inkapselen. U gebruikt het struct trefwoord om een structuurtype te definiëren:

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

Zie het artikel over ref-structuurtypen voor informatie over ref struct en readonly ref struct typen.

Structuurtypen hebben waardesemantiek. Dat wil gezegd: een variabele van een structuurtype bevat een exemplaar van het type. Standaard worden variabelewaarden gekopieerd bij de toewijzing, waarbij een argument aan een methode wordt doorgegeven en een methoderesultaat wordt geretourneerd. Voor structuurvariabelen wordt een exemplaar van het type gekopieerd. Zie Waardetypen voor meer informatie.

Normaal gesproken gebruikt u structuurtypen om kleine gegevensgerichte typen te ontwerpen die weinig of geen gedrag bieden. .NET gebruikt bijvoorbeeld structuurtypen om een getal (zowel geheel getal als reëel) weer te geven, een Booleaanse waarde, een Unicode-teken, een tijdexemplaren. Als u zich richt op het gedrag van een type, kunt u overwegen een klasse te definiëren. Klassetypen hebben verwijzingssemantiek. Dat wil gezegd: een variabele van een klassetype bevat een verwijzing naar een exemplaar van het type, niet naar het exemplaar zelf.

Omdat structuurtypen waardesemantiek hebben, raden we u aan onveranderbare structuurtypen te definiëren.

readonly Struct

U gebruikt de readonly wijzigingsfunctie om aan te geven dat een structuurtype onveranderbaar is. Alle gegevensleden van een readonly struct moeten als volgt alleen-lezen zijn:

Dat garandeert dat geen enkel lid van een readonly struct de status van de struct wijzigt. Dit betekent dat andere exemplaarleden, behalve constructors, impliciet readonlyzijn.

Notitie

In een readonly struct kan een gegevenslid van een onveranderbaar verwijzingstype nog steeds de eigen status muteren. U kunt bijvoorbeeld een List<T> exemplaar niet vervangen, maar u kunt er nieuwe elementen aan toevoegen.

De volgende code definieert een readonly struct met init-only eigenschapssetters:

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 exemplaarleden

U kunt de readonly wijzigingsfunctie ook gebruiken om aan te geven dat een exemplaarlid de status van een struct niet wijzigt. Als u het hele structuurtype niet kunt declareren als readonly, gebruikt u de readonly wijzigingsfunctie om de exemplaarleden te markeren die de status van de struct niet wijzigen.

Binnen een readonly exemplaarlid kunt u niet toewijzen aan de instantievelden van de structuur. Een readonly lid kan echter een niet-lidreadonly aanroepen. In dat geval maakt de compiler een kopie van het structuurexemplaar en roept het niet-lidreadonly op die kopie aan. Als gevolg hiervan wordt het oorspronkelijke structuurexemplaren niet gewijzigd.

Normaal gesproken past u de readonly wijzigingsfunctie toe op de volgende soorten instantieleden:

  • Methoden:

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

    U kunt de readonly wijzigingsfunctie ook toepassen op methoden die methoden overschrijven die zijn gedeclareerd in System.Object:

    public readonly override string ToString() => $"({X}, {Y})";
    
  • eigenschappen en indexeerfuncties:

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

    Als u de readonly wijzigingsfunctie wilt toepassen op beide toegangsrechten van een eigenschap of indexeerfunctie, past u deze toe in de declaratie van de eigenschap of indexeerfunctie.

    Notitie

    De compiler declareert een get toegangsfunctie van een automatisch geïmplementeerde eigenschap als readonly, ongeacht de aanwezigheid van de readonly wijzigingsfunctie in een eigenschapsdeclaratie.

    U kunt de readonly wijzigingsfunctie toepassen op een eigenschap of indexeerfunctie met een init toegangsfunctie:

    public readonly double X { get; init; }
    

U kunt de readonly wijzigingsfunctie toepassen op statische velden van een structuurtype, maar niet op andere statische leden, zoals eigenschappen of methoden.

De compiler kan gebruikmaken van de readonly wijzigingsfunctie voor prestatieoptimalisaties. Zie Toewijzingen vermijden voor meer informatie.

Niet-destructieve mutatie

Vanaf C# 10 kunt u de with expressie gebruiken om een kopie van een exemplaar van het structuurtype te produceren met de opgegeven eigenschappen en velden die zijn gewijzigd. U gebruikt de syntaxis van de object-initialisatiefunctie om op te geven welke leden moeten worden gewijzigd en de nieuwe waarden, zoals in het volgende voorbeeld wordt weergegeven:

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

Vanaf C# 10 kunt u recordstructuurtypen definiëren. Recordtypen bieden ingebouwde functionaliteit voor het inkapselen van gegevens. U kunt zowel record structreadonly record struct typen als typen definiëren. Een recordstruct kan geen ref struct. Zie Records voor meer informatie en voorbeelden.

Inlinematrices

Vanaf C# 12 kunt u inlinematrices declareren als een struct type:

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

Een inlinematrix is een structuur die een aaneengesloten blok N-elementen van hetzelfde type bevat. Het is een safe-code-equivalent van de vaste bufferdeclaratie die alleen beschikbaar is in onveilige code. Een inlinematrix is een struct met de volgende kenmerken:

  • Het bevat één veld.
  • De struct geeft geen expliciete indeling op.

Daarnaast valideert de compiler het System.Runtime.CompilerServices.InlineArrayAttribute kenmerk:

  • De lengte moet groter zijn dan nul (> 0).
  • Het doeltype moet een struct zijn.

In de meeste gevallen kan een inlinematrix worden geopend als een matrix, zowel om waarden te lezen als te schrijven. Daarnaast kunt u de bereik- en indexoperators gebruiken.

Er gelden minimale beperkingen voor het type enkel veld. Het kan geen aanwijzertype zijn, maar kan elk verwijzingstype of een waardetype zijn. U kunt inlinematrices gebruiken met vrijwel elke C#-gegevensstructuur.

Inlinematrices zijn een geavanceerde taalfunctie. Ze zijn bedoeld voor scenario's met hoge prestaties waarbij een inline, aaneengesloten blok elementen sneller is dan andere alternatieve gegevensstructuren. Meer informatie over inlinematrices vindt u in de functiespeclet

Initialisatie en standaardwaarden voor struct

Een variabele van een struct type bevat rechtstreeks de gegevens voor die struct. Hiermee wordt een onderscheid gemaakt tussen een niet-geïnitialiseerde struct, die de standaardwaarde en een geïnitialiseerde structwaarde bevat, waarin waarden worden opgeslagen die zijn ingesteld door deze samen te stellen. Denk bijvoorbeeld aan de volgende 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 ()
}

Zoals in het voorgaande voorbeeld wordt weergegeven, negeert de standaardwaardeexpressie een constructor zonder parameters en produceert de standaardwaarde van het structuurtype. Met een structuurtype-matrixexemptatie wordt ook een constructor zonder parameters genegeerd en wordt een matrix gegenereerd die is gevuld met de standaardwaarden van een structuurtype.

De meest voorkomende situatie waarin u standaardwaarden ziet, is in matrices of in andere verzamelingen waarin interne opslag blokken variabelen bevat. In het volgende voorbeeld wordt een matrix van 30 TemperatureRange structuren gemaakt, die elk de standaardwaarde hebben:

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

Alle lidvelden van een struct moeten zeker worden toegewezen wanneer deze worden gemaakt, omdat struct typen hun gegevens rechtstreeks opslaan. De default waarde van een struct heeft zeker alle velden toegewezen aan 0. Alle velden moeten zeker worden toegewezen wanneer een constructor wordt aangeroepen. U initialiseert velden met behulp van de volgende mechanismen:

  • U kunt veld initialisaties toevoegen aan een veld of automatisch geïmplementeerde eigenschap.
  • U kunt velden of automatische eigenschappen initialiseren in de hoofdtekst van de constructor.

Vanaf C# 11 voegt de compiler code toe aan de constructor waarmee deze velden worden geïnitialiseerd naar de standaardwaarde als u niet alle velden in een struct initialiseert. De compiler voert de gebruikelijke definitieve toewijzingsanalyse uit. Velden die worden geopend voordat ze worden toegewezen, of niet zeker toegewezen wanneer de constructor klaar is met uitvoeren, worden de standaardwaarden toegewezen voordat de constructortekst wordt uitgevoerd. Als this deze wordt geopend voordat alle velden worden toegewezen, wordt de struct geïnitialiseerd op de standaardwaarde voordat de hoofdtekst van de constructor wordt uitgevoerd.

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

Elk struct heeft een public parameterloze constructor. Als u een parameterloze constructor schrijft, moet deze openbaar zijn. Als een struct veldinitiitiizers declareert, moet deze expliciet een constructor declareren. Deze constructor hoeft niet parameterloos te zijn. Als een struct een veld-initializer maar geen constructors declareert, rapporteert de compiler een fout. Elke expliciet gedeclareerde constructor (met parameters of parameterloos) voert alle veldinitiitiizers voor die struct uit. Alle velden zonder een veldinitiizer of een toewijzing in een constructor worden ingesteld op de standaardwaarde. Zie voor meer informatie de functievoorstelnotitie voor parameterloze struct constructors .

Vanaf C# 12 struct kunnen typen een primaire constructor definiëren als onderdeel van de declaratie. Primaire constructors bieden een beknopte syntaxis voor constructorparameters die in de struct hele hoofdtekst kunnen worden gebruikt, in elke liddeclaratie voor die struct.

Als alle instantievelden van een structuurtype toegankelijk zijn, kunt u deze ook instantiëren zonder de new operator. In dat geval moet u alle exemplaarvelden initialiseren vóór het eerste gebruik van het exemplaar. In het volgende voorbeeld ziet u hoe u dit doet:

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

In het geval van de ingebouwde waardetypen gebruikt u de bijbehorende letterlijke waarden om een waarde van het type op te geven.

Beperkingen met het ontwerp van een structuurtype

Structs hebben de meeste mogelijkheden van een klassetype . Er zijn enkele uitzonderingen en enkele uitzonderingen die zijn verwijderd in recentere versies:

  • Een structuurtype kan niet overnemen van een ander klasse- of structuurtype en kan niet de basis van een klasse zijn. Een structuurtype kan echter interfaces implementeren.
  • U kunt geen finalizer binnen een structuurtype declareren.
  • Vóór C# 11 moet een constructor van een structuurtype alle exemplaarvelden van het type initialiseren.

Structuurvariabelen doorgeven per verwijzing

Wanneer u een structuurtypevariabele doorgeeft aan een methode als argument of een structuurtypewaarde retourneert van een methode, wordt het hele exemplaar van een structuurtype gekopieerd. Pass by-waarde kan van invloed zijn op de prestaties van uw code in scenario's met hoge prestaties die betrekking hebben op grote structuurtypen. U kunt het kopiëren van waarden voorkomen door een structuurvariabele door te geven op basis van verwijzing. Gebruik de refparameteraanpassingen , inoutof ref readonly methodeparameteraanpassingen om aan te geven dat een argument moet worden doorgegeven met verwijzing. Gebruik ref returns om een methoderesultaat te retourneren op basis van verwijzing. Zie Toewijzingen vermijden voor meer informatie.

structbeperking

U gebruikt ook het struct trefwoord in de struct beperking om op te geven dat een typeparameter een niet-null-waardetype is. Zowel structuur- als opsommingstypen voldoen aan de struct beperking.

Conversies

Voor elk structuurtype (met uitzondering ref struct van typen) bestaan boksen en het opheffen van conversies van en naar de System.ValueType en System.Object typen. Er bestaan ook conversies voor boksen en uitpakken tussen een structuurtype en elke interface die wordt geïmplementeerd.

C#-taalspecificatie

Zie de sectie Structs van de C#-taalspecificatie voor meer informatie.

Zie de volgende opmerkingen over functievoorstel voor meer informatie over struct functies:

Zie ook