Delen via


16 Structuren

16.1 Algemeen

Structs zijn vergelijkbaar met klassen omdat ze gegevensstructuren vertegenwoordigen die gegevensleden en functieleden kunnen bevatten. In tegenstelling tot klassen zijn structs echter waardetypen en vereisen geen heaptoewijzing. Een variabele van een struct type bevat rechtstreeks de gegevens van de struct, terwijl een variabele van een klassetype een verwijzing naar de gegevens bevat, de laatste ook wel een object genoemd.

Opmerking: Structs zijn met name handig voor kleine gegevensstructuren die waardesemantiek hebben. Complexe getallen, punten in een coördinatensysteem of sleutel-waardeparen in een woordenlijst zijn allemaal goede voorbeelden van structs. De sleutel voor deze gegevensstructuren is dat ze weinig gegevensleden hebben, dat ze geen gebruik hoeven te maken van overname- of verwijzingsemantiek, maar dat ze handig kunnen worden geïmplementeerd met behulp van waardesemantiek waarbij de toewijzing de waarde kopieert in plaats van de verwijzing. eindnotitie

Zoals beschreven in §8.3.5 zijn de eenvoudige typen van C#, zoals int, doubleen bool, eigenlijk alle structtypen.

16.2 Struct-declaraties

16.2.1 Algemeen

Een struct_declaration is een type_declaration (§14.7) die een nieuwe struct declareert:

struct_declaration
    : attributes? struct_modifier* 'ref'? 'partial'? 'struct'
      identifier type_parameter_list? struct_interfaces?
      type_parameter_constraints_clause* struct_body ';'?
    ;

Een struct_declaration bestaat uit een optionele set kenmerken (§22), gevolgd door een optionele set struct_modifiers (§16.2.2), gevolgd door een optionele ref wijzigingsfunctie (§16.2.3), gevolgd door een optionele gedeeltelijke wijziging (§15.2.7), gevolgd door het trefwoord struct en een identifier die de struct een naam geeft, gevolgd door een optionele type_parameter_list specificatie (§15.2.3), gevolgd door een optionele struct_interfaces specificatie (§16.2.5), gevolgd door een optionele specificatie van type_parameter_constraints-componenten (§15.2.5), gevolgd door een struct_body (§16.2.6), eventueel gevolgd door een puntkomma.

Een structuurverklaring levert geen type_parameter_constraints_clauses, tenzij ook een type_parameter_listwordt geleverd.

Een struct-verklaring die een type_parameter_list levert, is een algemene structdeclaratie. Bovendien is elke struct die genest is in een algemene klassedeclaratie of een algemene structdeclaratie zelf een algemene struct-declaratie, aangezien typeargumenten voor het betreffende type worden opgegeven om een geconstrueerd type te maken (§8.4).

Een structverklaring met een ref trefwoord mag geen struct_interfaces deel hebben.

16.2.2 Struct-modifiers

Een struct_declaration kan eventueel een reeks struct_modifier sbevatten:

struct_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'readonly'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier (§23.2) is alleen beschikbaar in onveilige code (§23).

Het is een compilatiefout voor dezelfde wijziging die meerdere keren in een struct-declaratie wordt weergegeven.

Behalve readonly hebben de modifiers van een struct-declaratie dezelfde betekenis als die van een klassedeclaratie (§15.2.2).

De readonly wijzigingsfunctie geeft aan dat de struct_declaration een type declareert waarvan exemplaren onveranderbaar zijn.

Een readonly struct heeft de volgende beperkingen:

  • Elk van de instantievelden wordt ook gedeclareerd readonly.
  • Het mag geen veldachtige gebeurtenissen verklaren (§15.8.2).

Wanneer een instantie van een readonly-struct wordt doorgegeven aan een methode, wordt het behandeld als een invoerargument/parameter, waardoor schrijftoegang tot enige instantievelden niet is toegestaan (behalve door constructors).

16.2.3 Ref modifier

De ref wijzigingsfunctie geeft aan dat de struct_declaration een type declareert waarvan exemplaren worden toegewezen aan de uitvoeringsstack. Typen zoals deze worden ref struct-typen genoemd. De ref wijzigingsfunctie verklaart dat instanties ref-achtige velden mogen bevatten en mogen niet worden gekopieerd uit de veilige context (§16.4.15). De regels voor het bepalen van de veilige context van een refstruct worden beschreven in §16.4.15.

Het is een compilatiefout als een ref struct-type wordt gebruikt in een van de volgende contexten:

  • Als het elementtype van een array.
  • Als het gedeclareerde type van een veld van een klasse of een struct die niet over de ref wijzigingsfunctie beschikt.
  • Beperkingen tot System.ValueType of System.Object.
  • Als typeargument.
  • Als het type van een tupel-element.
  • Een asynchrone methode.
  • Een iterator.
  • Er is geen conversie van een ref struct type naar het type object of het type System.ValueType.
  • Een ref struct type wordt niet gedeclareerd om een interface te implementeren.
  • Een instantiemethode die in object of in System.ValueType is gedeclareerd maar niet in een ref struct type is overschreven, mag niet worden aangeroepen met een ontvanger van dat ref struct type.
  • Een instantiemethode van een ref struct type mag niet worden vastgelegd door het omzetten van een methodengroep naar een gedelegeerd type.
  • Een ref-struct mag niet worden gevangen door een lambda-expressie of een lokale functie.

Opmerking: Een ref struct mag geen async exemplaarmethoden declareren noch een yield return of yield break instructie gebruiken binnen een instantiemethode, omdat de impliciete this parameter in die contexten niet kan worden gebruikt. eindnotitie

Deze beperkingen zorgen ervoor dat een variabele van het ref struct type niet verwijst naar stackgeheugen dat niet langer geldig is of op variabelen die niet langer geldig zijn.

16.2.4 Gedeeltelijke wijziging

De partial wijzigingsfunctie geeft aan dat deze struct_declaration een gedeeltelijke typedeclaratie is. Meerdere gedeeltelijke struct-declaraties met dezelfde naam binnen een tussenliggende naamruimte of typedeclaratie combineren tot één struct-declaratie, volgens de regels die zijn opgegeven in §15.2.7.

16.2.5 Structureninterfaces

Een structdeclaratie kan een struct_interfaces specificatie bevatten. In dat geval wordt gezegd dat de struct rechtstreeks de opgegeven interfacetypen implementeert. Voor een samengesteld structtype, met inbegrip van een geneste type dat is gedeclareerd in een algemene typedeclaratie (§15.3.9.7), wordt elk geïmplementeerd interfacetype verkregen door voor elke type_parameter in de opgegeven interface de bijbehorende type_argument van het samengestelde type te vervangen.

struct_interfaces
    : ':' interface_type_list
    ;

De verwerking van interfaces op meerdere delen van een gedeeltelijke structdeclaratie (§15.2.7) wordt verder besproken in §15.2.4.3.

Interface-implementaties worden verder besproken in §18.6.

16.2.6 Struct-lichaam

De struct_body van een struct definieert de leden van de struct.

struct_body
    : '{' struct_member_declaration* '}'
    ;

16.3 Struct-leden

De leden van een struct bestaan uit de leden die zijn geïntroduceerd door de struct_member_declarations en de leden die zijn overgenomen van het type System.ValueType.

struct_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | operator_declaration
    | constructor_declaration
    | static_constructor_declaration
    | type_declaration
    | fixed_size_buffer_declaration   // unsafe code support
    ;

fixed_size_buffer_declaration (§23.8.2) is alleen beschikbaar in onveilige code (§23).

Opmerking: alle soorten class_member_declarations behalve finalizer_declaration zijn ook struct_member_declarations. eindnotitie

Behalve de verschillen die in §16.4 zijn vermeld, gelden de beschrijvingen van klasseleden die in §15.3 tot en met §15.12 zijn vermeld, ook voor structleden.

16.4 Klassen- en structenverschillen

16.4.1 Algemeen

Structs verschillen op een aantal belangrijke manieren van klassen.

  • Structs zijn waardetypen (§16.4.2).
  • Alle structtypen nemen impliciet over van de klasse System.ValueType (§16.4.3).
  • Bij toewijzing aan een variabele van een structtype wordt een kopie gemaakt van de waarde die wordt toegewezen (§16.4.4).
  • De standaardwaarde van een struct is de waarde die wordt geproduceerd door alle velden in te stellen op de standaardwaarde (§16.4.5).
  • Boksen en uitpakken worden gebruikt om te converteren tussen een structtype en bepaalde verwijzingstypen (§16.4.6).
  • De betekenis van this is anders binnen structleden (§16.4.7).
  • Declaraties van instantievelden voor een struct mogen geen variabele initializers bevatten (§16.4.8).
  • Een struct is niet toegestaan om een constructor voor een parameterloze instantie te declareren (§16.4.9).
  • Een struct is niet toegestaan om een finalizer te declareren.
  • Gebeurtenisdeclaraties, eigenschapsdeclaraties, eigenschapstoegangsfuncties, indexerdeclaraties en methodedeclaraties mogen de modifier readonly hebben, terwijl dat meestal niet is toegestaan voor dezelfde soorten leden in klassen.

16.4.2 Waardesemantiek

Structs zijn waardetypen (§8.3) en worden gezegd dat ze waardesemantiek hebben. Klassen zijn daarentegen verwijzingstypen (§8.2) en worden gezegd dat ze verwijzingssemantiek hebben.

Een variabele van een structtype bevat rechtstreeks de gegevens van de struct, terwijl een variabele van een klassetype een verwijzing bevat naar een object dat de gegevens bevat. Wanneer een struct B een exemplaarveld van het type A bevat en A een structtype is, is het een fout bij het compileren als A afhankelijk is van B of een type dat is samengesteld uit B. Een struct-Xis rechtstreeks afhankelijk van een struct-Y als X een exemplaarveld van het type Ybevat. Gezien deze definitie is de volledige set van structs waarvan een struct afhankelijk is, de transitieve sluiting van de relatie rechtstreeks afhankelijk van.

Voorbeeld:

struct Node
{
    int data;
    Node next; // error, Node directly depends on itself
}

is een fout omdat Node een exemplaarveld van zijn eigen type bevat. Nog een voorbeeld

struct A { B b; }
struct B { C c; }
struct C { A a; }

is een fout omdat elk van de typen A, Ben C afhankelijk is van elkaar.

eindvoorbeeld

Met klassen is het mogelijk dat twee variabelen verwijzen naar hetzelfde object en dus mogelijk zijn voor bewerkingen op één variabele die van invloed zijn op het object waarnaar wordt verwezen door de andere variabele. Met structs hebben de variabelen elk hun eigen kopie van de gegevens (behalve in het geval van parameters die door referentie worden doorgegeven), en het is niet mogelijk dat bewerkingen op de ene de andere beïnvloeden. Behalve wanneer expliciet nullable is (§8.3.12), is het niet mogelijk dat waarden van een structtype null zijn.

Opmerking: Als een struct een verwijzingsveld bevat, kan de inhoud van het object waarnaar wordt verwezen, worden gewijzigd door andere bewerkingen. De waarde van het veld zelf, d.w.w., welk object het verwijst, kan echter niet worden gewijzigd door een mutatie van een andere structwaarde. eindnotitie

Voorbeeld: Op basis van het volgende

struct Point
{
    public int x, y;

    public Point(int x, int y) 
    {
        this.x = x;
        this.y = y;
    }
}

class A
{
    static void Main()
    {
        Point a = new Point(10, 10);
        Point b = a;
        a.x = 100;
        Console.WriteLine(b.x);
    }
}

de uitvoer is 10. De toewijzing van a aan b creëert een kopie van de waarde, waardoor b dus niet wordt beïnvloed door de toewijzing aan a.x. Was Point in plaats daarvan gedeclareerd als een klasse, zou de uitvoer zijn 100 omdat a en b zou verwijzen naar hetzelfde object.

eindvoorbeeld

16.4.3 Overname

Alle structtypen nemen impliciet over van de klasse System.ValueType, die op zijn beurt overgenomen worden van klasse object. Een struct-declaratie kan een lijst met geïmplementeerde interfaces opgeven, maar het is niet mogelijk voor een struct-declaratie om een basisklasse op te geven.

Struct-typen zijn nooit abstract en worden altijd impliciet verzegeld. De abstract en sealed modifiers zijn daarom niet toegestaan in een structdeclaratie.

Aangezien overerving niet wordt ondersteund voor structs, kan de gedeclareerde toegankelijkheid van een struct-lid niet protected, private protected, of protected internal zijn.

Functieleden in een struct kunnen niet abstract of virtueel zijn en de override modifier mag alleen methoden overschrijven die zijn overgenomen van System.ValueType.

16.4.4 Toewijzing

Wanneer een waarde wordt toegewezen aan een variabele van een structtype, wordt er een kopie van die waarde gemaakt. Dit verschilt van toewijzing tot een variabele van een klassetype, waarmee de verwijzing wordt gekopieerd, maar niet het object dat door de verwijzing wordt geïdentificeerd.

Net als bij een toewijzing, wanneer een struct als waardeparameter wordt doorgegeven of als resultaat van een functie-lid wordt geretourneerd, wordt er een kopie van de struct gemaakt. Een struct kan worden doorgegeven door verwijzing naar een functielid met behulp van een by-reference-parameter.

Wanneer een eigenschap of indexeerfunctie van een struct het doel is van een toewijzing, wordt de exemplaarexpressie die is gekoppeld aan de toegang tot de eigenschap of indexeerfunctie geclassificeerd als een variabele. Als de exemplaarexpressie is geclassificeerd als een waarde, treedt er een compilatietijdfout op. Dit wordt nader beschreven in §12.21.2.

16.4.5 Standaardwaarden

Zoals beschreven in §9.3 worden verschillende soorten variabelen automatisch geïnitialiseerd naar hun standaardwaarde wanneer ze worden gemaakt. Voor variabelen van klassetypen en andere referentietypen is nulldeze standaardwaarde. Omdat structs echter waardetypen zijn die niet kunnen zijn null, is de standaardwaarde van een struct de waarde die wordt geproduceerd door alle waardetypevelden in te stellen op de standaardwaarde en alle verwijzingstypevelden op null.

Voorbeeld: Verwijzen naar de Point hierboven gedeclareerde struct, het voorbeeld

Point[] a = new Point[100];

initialiseert elk Point in de matrix op de waarde die wordt geproduceerd door de x en y velden in te stellen op nul.

eindvoorbeeld

De standaardwaarde van een struct komt overeen met de waarde die wordt geretourneerd door de standaardconstructor van de struct (§8.3.3). In tegenstelling tot een klasse is een struct niet toegestaan om een constructor voor een parameterloze instantie te declareren. In plaats daarvan heeft elke struct impliciet een constructor voor een exemplaar zonder parameter, die altijd de waarde retourneert die het resultaat is van het instellen van alle velden op de standaardwaarden.

Opmerking: Structs moeten zodanig worden ontworpen dat de standaard initialisatiestatus als een geldige status wordt beschouwd. In het voorbeeld

struct KeyValuePair
{
    string key;
    string value;

    public KeyValuePair(string key, string value)
    {
        if (key == null || value == null)
        {
            throw new ArgumentException();
        }

        this.key = key;
        this.value = value;
    }
}

de door de gebruiker gedefinieerde exemplaarconstructor beschermt zich alleen tegen null waarden waar deze expliciet wordt aangeroepen. In gevallen waarin een KeyValuePair variabele onderhevig is aan standaardwaarde-initialisatie, zullen de key- en value-velden null zijn en moet de structuur worden voorbereid om deze status te verwerken.

eindnotitie

16.4.6 Boksen en uitpakken

Een waarde van een klassetype kan worden geconverteerd naar een type object of naar een interfacetype dat door de klasse wordt geïmplementeerd door de verwijzing als een ander type tijdens het compileren te behandelen. Op dezelfde manier kan een waarde van het type object of een waarde van een interfacetype worden geconverteerd naar een klassetype zonder de verwijzing te wijzigen (maar in dit geval is een controle van het runtime-type vereist).

Omdat structs geen verwijzingstypen zijn, worden deze bewerkingen anders geïmplementeerd voor structtypen. Wanneer een waarde van een structtype wordt geconverteerd naar bepaalde referentietypen (zoals gedefinieerd in §10.2.9), vindt er een boksbewerking plaats. Ook wanneer een waarde van bepaalde verwijzingstypen (zoals gedefinieerd in §10.3.7) wordt teruggezet naar een structtype, vindt er een uitboxbewerking plaats. Een belangrijk verschil met dezelfde bewerkingen voor klassetypen is dat met boksen en uitpakken de structwaarde naar of uit het boxed-exemplaar wordt gekopieerd.

Opmerking: Na een boxing- of unboxingbewerking worden wijzigingen aangebracht in het unboxed struct niet weerspiegeld in het boxed struct. eindnotitie

Zie §10.2.9 en §10.3.7 voor meer informatie over boksen en uitpakken.

16.4.7 Betekenis van dit

De betekenis van this een struct verschilt van de betekenis van this een klasse, zoals beschreven in §12.8.14. Wanneer een structtype een virtuele methode overschrijft die is overgenomen van System.ValueType (zoals Equals, GetHashCode of ToString), vindt bij de aanroep van de virtuele methode via een exemplaar van het structtype geen boksen plaats. Dit geldt zelfs wanneer de struct wordt gebruikt als een typeparameter en de aanroep plaatsvindt via een exemplaar van het type parametertype.

Voorbeeld:

struct Counter
{
    int value;
    public override string ToString() 
    {
        value++;
        return value.ToString();
    }
}

class Program
{
    static void Test<T>() where T : new()
    {
        T x = new T();
        Console.WriteLine(x.ToString());
        Console.WriteLine(x.ToString());
        Console.WriteLine(x.ToString());
    }

    static void Main() => Test<Counter>();
}

De uitvoer van het programma is:

1
2
3

Hoewel het een slechte stijl is wanneer ToString bijwerkingen heeft, toont het voorbeeld aan dat er geen boxing heeft plaatsgevonden voor de drie aanroepen van x.ToString().

eindvoorbeeld

Op dezelfde manier treedt boksen nooit impliciet op bij het openen van een lid op een parameter van een beperkt type wanneer het lid wordt geïmplementeerd binnen het waardetype. Stel dat een interface ICounter een methode Incrementbevat, die kan worden gebruikt om een waarde te wijzigen. Als ICounter wordt gebruikt als een beperking, wordt de implementatie van de Increment-methode aangeroepen met een verwijzing naar de variabele waarop Increment is aangeroepen, nooit een ingepakte kopie.

Voorbeeld:

interface ICounter
{
    void Increment();
}

struct Counter : ICounter
{
    int value;

    public override string ToString() => value.ToString();

    void ICounter.Increment() => value++;
}

class Program
{
    static void Test<T>() where T : ICounter, new()
    {
        T x = new T();
        Console.WriteLine(x);
        x.Increment();              // Modify x
        Console.WriteLine(x);
        ((ICounter)x).Increment();  // Modify boxed copy of x
        Console.WriteLine(x);
    }

    static void Main() => Test<Counter>();
}

De eerste aanroep van Increment wijzigt de waarde in de variabele x. Dit is niet gelijk aan de tweede aanroep van Increment, die de waarde wijzigt in een vakkenkopie van x. De uitvoer van het programma is dus:

0
1
1

eindvoorbeeld

16.4.8 Veld initializers

Zoals beschreven in §16.4.5, bestaat de standaardwaarde van een struct uit de waarde die het resultaat is van het instellen van alle waardetypevelden op de standaardwaarde en alle verwijzingstypevelden op null. Daarom staat een struct niet toe dat declaraties van instantievelden variabele initializers bevatten. Deze beperking is alleen van toepassing op exemplaarvelden. Statische velden van een struct mogen variabele initializers bevatten.

Voorbeeld: Het volgende

struct Point
{
    public int x = 1; // Error, initializer not permitted
    public int y = 1; // Error, initializer not permitted
}

is fout omdat de instantievelddeclaraties variabele-initialisaties bevatten.

eindvoorbeeld

Een field_declaration die rechtstreeks binnen een struct_declaration met de struct_modifierreadonly wordt aangegeven, heeft de field_modifierreadonly.

16.4.9 Constructeurs

In tegenstelling tot een klasse is een struct niet toegestaan om een constructor voor een parameterloze instantie te declareren. In plaats daarvan heeft elke struct impliciet een constructor voor een exemplaar zonder parameter, die altijd de waarde retourneert die het resultaat is van het instellen van alle waardetypevelden op de standaardwaarde en alle verwijzingstypevelden op null (§8.3.3). Een struct kan exemplaarconstructors met parameters declareren.

Voorbeeld: Op basis van het volgende

struct Point
{
    int x, y;

    public Point(int x, int y) 
    {
        this.x = x;
        this.y = y;
    }
}

class A
{
    static void Main()
    {
        Point p1 = new Point();
        Point p2 = new Point(0, 0);
    }
}

de instructies creëren zowel een Point met x als een y geïnitialiseerd naar nul.

eindvoorbeeld

Een constructor voor het struct-exemplaar mag geen initialisatiefunctie voor de constructor van het formulier base(argument_list) opnemen, waarbij argument_list optioneel is.

De this parameter van een struct-exemplaarconstructor komt overeen met een uitvoerparameter van het structtype. Als zodanig wordt this definitief toegewezen (§9.4) op elke locatie waar de constructor terugkeert. Op dezelfde manier kan het niet worden gelezen (zelfs impliciet) in de constructorbody voordat deze definitief wordt toegewezen.

Als de constructor van het struct-exemplaar een constructor-initialisator opgeeft, wordt deze initialisator beschouwd als een definitieve toewijzing aan this die plaatsvindt vóór het lichaam van de constructor. Daarom heeft het lichaam zelf geen initialisatievereisten.

Voorbeeld: Bekijk de implementatie van de instantieconstructor hieronder:

struct Point
{
    int x, y;

    public int X
    {
        set { x = value; }
    }

    public int Y 
    {
        set { y = value; }
    }

    public Point(int x, int y) 
    {
        X = x; // error, this is not yet definitely assigned
        Y = y; // error, this is not yet definitely assigned
    }
}

Er kan geen exemplaarfunctielid (inclusief de set-toegangsdeelnemers voor de eigenschappen X en Y) worden aangeroepen totdat alle velden van de struct die wordt samengesteld definitief zijn toegewezen. Houd er echter rekening mee dat als Point dit een klasse is in plaats van een struct, de instantieconstructor-implementatie is toegestaan. Er is één uitzondering op dit en dat omvat automatisch geïmplementeerde eigenschappen (§15.7.4). De regels voor zekere toewijzing (§12.21.2) maken specifiek een uitzondering voor toewijzing aan een auto-property van een structtype binnen een instantieconstructor van dat structtype: een dergelijke toewijzing wordt beschouwd als een zekere toewijzing van het verborgen backingveld van de auto-property. Het volgende is dus toegestaan:

struct Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y)
    {
        X = x; // allowed, definitely assigns backing field
        Y = y; // allowed, definitely assigns backing field
   }
}

eindvoorbeeld]

16.4.10 Statische constructors

Statische constructors voor structs volgen de meeste van dezelfde regels als voor klassen. De uitvoering van een statische constructor voor een structtype wordt geactiveerd door de eerste van de volgende gebeurtenissen in een toepassingsdomein:

  • Er wordt naar een statisch lid van het structtype verwezen.
  • Een expliciet gedeclareerde constructor van het structtype wordt aangeroepen.

Opmerking: Het maken van standaardwaarden (§16.4.5) van structtypen activeert de statische constructor niet. (Een voorbeeld hiervan is de oorspronkelijke waarde van elementen in een matrix.) eindnotitie

16.4.11 Eigenschappen

Een property_declaration (§15.7.1) voor een exemplaareigenschap in een struct_declaration kan de property_modifierreadonly bevatten. Een statische eigenschap mag deze wijziging echter niet bevatten.

Het is een compilatiefout om de status van een structuurvariabele van een instantie te wijzigen via een write-only eigenschap die in die structuur is gedeclareerd.

Het is een compilatiefout voor een automatisch geïmplementeerde eigenschap met een readonly modifier, om ook een set accessor te hebben.

Het is een fout bij de compilatie dat een automatisch geïmplementeerde eigenschap in een readonly struct een set accessor heeft.

Een automatisch geïmplementeerde eigenschap die in een readonly struct is gedeclareerd, heeft geen readonly modifier nodig, omdat ervan wordt uitgegaan dat de get accessor impliciet readonly is.

Het is een compilatiefout om een readonly modifier te hebben voor een eigenschap zelf, evenals voor één van de get en set accessors.

Het is een compilatietijdfout als een eigenschap een alleen-lezen modificator heeft op al zijn toegangsmethoden.

Opmerking: Als u de fout wilt corrigeren, verplaatst u de wijzigingsfunctie van de accessors naar de eigenschap zelf. eindnotitie

Automatisch geïmplementeerde eigenschappen (§15.7.4) maken gebruik van verborgen backingvelden, die alleen toegankelijk zijn voor de eigenschapstoegangsors.

Opmerking: Deze toegangsbeperking betekent dat constructors in structs met automatisch geïmplementeerde eigenschappen vaak een expliciete constructor-initialisatiefunctie nodig hebben waar ze anders geen behoefte aan hebben, om te voldoen aan de vereiste dat alle velden definitief worden toegewezen voordat een functielid wordt aangeroepen of de constructor retourneert. eindnotitie

16.4.12 Methoden

Een method_declaration (§15.6.1) voor een instantiemethode in een struct_declaration kan de method_modifierreadonly bevatten. Een statische methode mag die wijziging echter niet bevatten.

Het is een compilatiefout om de toestand van een instantie-structvariabele te wijzigen via een alleen-lezen-methode die in die struct is gedeclareerd.

Hoewel een readonly-methode een sibling-methode, een niet-readonly-methode, of eigenschap of indexeerfunctie kan aanroepen, resulteert dit in het maken van een impliciete kopie van this als een defensieve maatregel.

Een alleen-lezen methode kan een eigenschap of indexer settoegangsmethode aanroepen die alleen-lezen is. Als de accessor van een broerlid niet expliciet of impliciet alleen-lezen is, treedt er een compileerfout op.

Alle method_declarationvan een gedeeltelijke methode moeten een readonly wijzigingsfunctie hebben, of geen van hen heeft deze.

16.4.13 Indexeerfuncties

Een indexer_declaration (§15.9) voor een exemplaarindexeerfunctie in een struct_declaration kan de indexer_modifierreadonly bevatten.

Het is een compilatiefout om de status van een exemplaarstructvariabele te wijzigen via een alleen-lezen indexeerfunctie die in die struct is gedeclareerd.

Het is een compilatiefout om een readonly modifier op een indexer zelf te hebben, evenals op een van zijn get of set accessors.

Het is een fout bij compilatie wanneer een indexeerder een alleen-lezen-modificeerder heeft op al zijn accessors.

Opmerking: Als u de fout wilt corrigeren, verplaatst u de wijzigingsfunctie van de accessors naar de indexeerfunctie zelf. eindnotitie

16.4.14 Gebeurtenissen

Een event_declaration (§15.8.1) voor een exemplaar, een niet-veldachtige gebeurtenis in een struct_declaration de event_modifierreadonly kan bevatten. Een statische gebeurtenis mag deze wijziging echter niet bevatten.

16.4.15 Veilige contextrestrictie

16.4.15.1 Algemeen

Tijdens het compileren wordt elke expressie gekoppeld aan een context waarin dat exemplaar en alle velden veilig kunnen worden geopend, de veilige context. De veilige context is een context die een expressie omsluit, waarin het veilig is voor de waarde om te ontsnappen naar.

Elke expressie waarvan het compileringstijdtype geen ref struct is, heeft een 'safe-context' van de 'caller-context'.

Een default expressie heeft voor elk type een veilige context van de aanroepercontext.

Voor elke niet-standaarduitdrukking waarvan het type tijdens compilatietijd een refstruct is, is een veilige context gedefinieerd volgens de volgende secties.

De veilige contextrecords waarnaar een waarde kan worden gekopieerd. Gezien een toewijzing van een expressie E1 met een veilige context S1, naar een expressie E2 met een veilige context S2, is het een fout als S2 dit een bredere context is dan S1.

Er zijn drie verschillende veilige contextwaarden, hetzelfde als de ref-safe-contextwaarden die zijn gedefinieerd voor referentievariabelen (§9.7.2): declaratieblok, functielid en aanroepercontext. De veilige context van een expressie beperkt het gebruik ervan als volgt:

  • Voor een retourverklaring return e1is de veilige context van e1 de aanroepercontext.
  • Voor een toewijzing e1 = e2 is de veilige context van e2 ten minste zo breed als de veilige context van e1.

Voor een methode-aanroep als er een ref of out argument van een ref struct type is (inclusief de ontvanger, tenzij het type is readonly), met een veilige context S1, kan er geen argument (inclusief de ontvanger) een beperktere veilige context hebben dan S1.

16.4.15.2 Parameter veilige context

Een parameter van een verwijzingstype, inclusief de this parameter van een exemplaarmethode, heeft een veilige context van de aanroepercontext.

16.4.15.3 Veilige context voor lokale variabelen

Een lokale variabele van een ref struct type heeft de volgende veilige context:

  • Als de variabele een iteratievariabele van een foreach lus is, is de veilige context van de variabele hetzelfde als de veilige context van de expressie van de foreach lus.
  • Als de declaratie van de variabele een initialisatiefunctie heeft, is de veilige context van de variabele hetzelfde als de veilige context van die initialisatiefunctie.
  • Anders wordt de variabele niet geïnitialiseerd op het moment van declaratie en heeft deze een veilige context van de aanroepercontext.

16.4.15.4 Veld veilige context

Een verwijzing naar een veld e.F, waarbij het type F een verwijzingstype is, heeft een veilige context die hetzelfde is als de veilige context van e.

16.4.15.5 Operatoren

De toepassing van een door de gebruiker gedefinieerde operator wordt behandeld als een methodeaanroep (§16.4.15.6).

Voor een operator die een waarde oplevert, zoals e1 + e2 of c ? e1 : e2, is de veilige context van het resultaat de kleinste context tussen de veilige contexten van de operanden van de operator. Als gevolg hiervan is voor een unaire operator die een waarde oplevert, zoals +e, de veilige context van het resultaat de veilige context van de operand is.

Opmerking: De eerste operand van een voorwaardelijke operator is een bool, dus de veilige context is de aanroepercontext. Hierna volgt dat de resulterende veilige context de kleinste veilige context van de tweede en derde operand is. eindnotitie

16.4.15.6 Methode en eigenschap aanroepen

Een waarde die het resultaat is van een aanroep van een methode e1.M(e2, ...) of een eigenschapsaanroep e.P heeft als veilige context de kleinste van de volgende contexten:

  • bellercontext.
  • De veilige context van alle argumentexpressies (inclusief de ontvanger).

Een aanroep van een eigenschap (bij een get-aanroep of een set-aanroep) wordt behandeld als een methode-aanroep van de onderliggende methode volgens de bovenstaande regels.

16.4.15.7 stackalloc

Het resultaat van een stackalloc-expressie heeft een veilige context van functielid.

16.4.15.8 Constructor aanroepen

Een new expressie die een constructor aanroept, voldoet aan dezelfde regels als een methode-aanroep die wordt beschouwd om het type te retourneren dat wordt samengesteld.

Daarnaast is de veilige context het kleinste van de veilige contexten van alle argumenten en operanden van alle object initializer-expressies, recursief, als er een initialisatiefunctie aanwezig is.

Opmerking: Deze regels zijn afhankelijk Span<T> van het niet hebben van een constructor van de volgende vorm:

public Span<T>(ref T p)

Een dergelijke constructor maakt exemplaren van Span<T> gebruikt als velden die niet kunnen worden onderscheiden van een ref veld. De veiligheidsregels die in dit document worden beschreven, zijn afhankelijk van ref velden die geen geldige constructie in C# of .NET zijn. eindnotitie