16 structs

16.1 Allmänt

Structs liknar klasser eftersom de representerar datastrukturer som kan innehålla datamedlemmar och funktionsmedlemmar. Men till skillnad från klasser är structs värdetyper och kräver inte heap-allokering. En variabel av en struct typ innehåller direkt data för struct, medan en variabel av en klasstyp innehåller en referens till data, den senare kallas för ett objekt.

Obs! Structs är särskilt användbara för små datastrukturer som har värdesemantik. Komplexa tal, punkter i ett koordinatsystem eller nyckel/värde-par i en ordlista är alla bra exempel på structs. Nyckeln till dessa datastrukturer är att de har få datamedlemmar, att de inte kräver användning av arvs- eller referenssemantik, snarare kan de implementeras bekvämt med hjälp av värdesemantik där tilldelningen kopierar värdet i stället för referensen. slutkommentar

Enligt beskrivningen i §8.3.5 är de enkla typer som tillhandahålls av C#, som int, doubleoch bool, i själva verket alla structtyper.

16.2 Struct-deklarationer

16.2.1 Allmänt

En struct_declaration är en type_declaration (§14.8) som deklarerar en ny struct:

struct_declaration
    : non_record_struct_declaration
    | record_struct_declaration
    ;

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

record_struct_declaration
    : attributes? struct_modifier* 'partial'? 'record' 'struct'
      identifier type_parameter_list? delimited_parameter_list? struct_interfaces?
      type_parameter_constraints_clause* record_struct_body
    ;

record_struct_body
    : struct_body ';'?
    | ';'
    ;

En struct_declaration är för antingen en icke-post-struct eller en post struct.

En non_record_struct_declaration består av en valfri uppsättning attribut (§23), följt av en valfri uppsättning struct_modifier(§16.2.2), följt av en valfri ref modifierare (§16.2.3), följt av en valfri partiell modifierare (§15.2.7), följt av nyckelordet struct och en identifierare som namnger structen, följt av en valfri type_parameter_list specifikation (§15.2.3), följt av en valfri struct_interfaces specifikation (§16.2.5), följt av en valfri type_parameter_constraints-klausulsspecifikation (§15.2.5), följt av en struct_body (§16.2.6), eventuellt följt av semikolon.

En record_struct_declaration består av en valfri uppsättning attribut (§23), följt av en valfri uppsättning struct_modifier(§16.2.2), följt av en valfri partiell modifierare (§15.2.7), följt av nyckelordet record, följt av nyckelordet struct och en identifierare som namnger structen, följt av en valfri type_parameter_list specifikation (§15.2.3), följt av en valfri delimited_parameter_list specifikation (§15.2.1), följt av en valfri struct_interfaces specifikation (§16.2.5), följt av en valfri type_parameter_constraints-klausulsspecifikation (§15.2.5), följt av en record_struct_body.

En struct_declaration får inte tillhandahålla type_parameter_constraints_clauseom den inte även tillhandahåller en type_parameter_list.

En struct_declaration som tillhandahåller en type_parameter_list är en allmän structdeklaration. Dessutom är varje struct kapslad inuti en generisk klassdeklaration eller en generisk structdeklaration i sig en generisk structdeklaration, eftersom typargument för den innehållande typen ska levereras för att skapa en konstruerad typ (§8.4).

En non_record_struct_declaration som innehåller en ref modifierare får inte ha en struct_interfaces del.

En record_struct_declaration som har en delimited_parameter_list deklarerar en positionell poststruct.

Högst en record_struct_declaration som innehåller partial kan ge en delimited_parameter_list.

Parametrarna i delimited_parameter_list får dock in inte ha ref, out eller this modifierare, och params modifierare är tillåtna. För en record_struct_declaration är record_struct_bodys {}, {};och ; likvärdiga. De visar alla att de enda medlemmarna är de som syntetiseras av kompilatorn (§16.4).

16.2.2 Strukturmodifikatorer

En struct_declaration kan eventuellt innehålla en sekvens med struct_modifiers:

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

unsafe_modifier (§24.2) är endast tillgänglig i osäker kod (§24).

Det är ett kompileringsfel för samma modifierare som visas flera gånger i en struct-deklaration.

Med undantag för readonlyhar modifierarna för en structdeklaration samma betydelse som en klassdeklaration (§15.2.2).

Modifieraren readonly anger att struct_declaration deklarerar en typ vars instanser är oföränderliga.

En skrivskyddad struktur har följande begränsningar:

  • Vart och ett av dess instansfält ska också deklareras readonly.
  • Den skall inte deklarera några fältliknande händelser (§15.8.2).

När en instans av en skrivskyddad struct skickas till en metod behandlas den this som ett indataargument/parameter, vilket inte tillåter skrivåtkomst till instansfält (förutom konstruktorer).

16.2.3 Refmodifierare

Modifieraren ref anger att non_record_struct_declaration deklarerar en typ vars instanser allokeras i körningsstacken. Dessa typer kallas referensstruktureringstyper. Modifieraren ref deklarerar att instanser kan innehålla ref-liknande fält och får inte kopieras ur sin säkra kontext (§16.5.15). Reglerna för att fastställa en referens structs säkra kontext beskrivs i §16.5.15.

Det är ett kompileringsfel om en referens struct-typ används i någon av följande kontexter:

  • Som elementtyp för en matris.
  • Som den deklarerade typen av ett fält i en klass eller en struct som inte har ref modifieraren.
  • Som ett typargument.
  • Som typ av ett tupppelelement.
  • I en asynkron metod.
  • I en iterator.
  • Som mottagartyp för en metodgruppkonvertering från en instansmetod till en ombudstyp.
  • Som en insamlad variabel i ett lambda-uttryck eller en lokal funktion.

Dessutom gäller följande begränsningar för en ref struct typ:

  • En ref struct typ får inte boxas till System.ValueType eller System.Object.
  • En ref struct typ får inte deklareras för att implementera något gränssnitt.
  • En instansmetod som deklareras i object eller i System.ValueType men som inte åsidosätts i en ref struct typ ska inte anropas med en mottagare av den ref struct typen.

Obs! En ref struct får inte deklarera async instansmetoder eller använda en yield return-yield breaksats i en instansmetod, eftersom den implicita this-parametern inte kan användas i dessa kontexter. slutkommentar

Dessa begränsningar säkerställer att en variabel av ref struct typen inte refererar till stackminne som inte längre är giltigt eller till variabler som inte längre är giltiga.

16.2.4 Partiell modifierare

Modifieraren partial anger att den här struct_declaration är en partiell typdeklaration. Flera partiella structdeklarationer med samma namn inom ett omslutande namnområde eller typdeklaration kombineras för att bilda en structdeklaration, enligt de regler som anges i §15.2.7.

16.2.5 Struct-gränssnitt

En structdeklaration kan innehålla en struct_interfaces specifikation, i vilket fall structen sägs direkt implementera de angivna gränssnittstyperna. För en konstruerad structtyp, inklusive en kapslad typ som deklarerats inom en allmän typdeklaration (§15.3.9.7), erhålls varje implementerad gränssnittstyp genom att ersätta, för varje type_parameter i det angivna gränssnittet, motsvarande type_argument av den konstruerade typen.

struct_interfaces
    : ':' interface_type_list
    ;

Hanteringen av gränssnitt på flera delar av en partiell structdeklaration (§15.2.7) diskuteras ytterligare i §15.2.4.3.

Gränssnittsimplementeringar diskuteras ytterligare i §19.6.

16.2.6 Struct innehåll

Struct_body för en struct definierar medlemmarna i structen.

struct_body
    : '{' struct_member_declaration* '}'
    ;

16.3 Strukturmedlemmar

16.3.1 Allmänt

Medlemmarna i en struct består av de medlemmar som introduceras av dess struct_member_declarations och de medlemmar som ärvts från typen System.ValueType. För en post-struct innehåller medlemsuppsättningen även de syntetiserade medlemmar som genereras av kompilatorn (§synth-members).

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 (§24.8.2) är endast tillgänglig i osäker kod (§24).

Obs! Alla typer av class_member_declarationutom finalizer_declaration är också struct_member_declarations. slutkommentar

Förutom de skillnader som anges i §16.5 gäller även de beskrivningar av klassmedlemmar som anges i §15.3 till och med §15.12 för structmedlemmar.

Det är ett fel att ett instansfält i en post struct har en osäker typ.

16.3.2 Readonly medlemmar

En instansmedlemsdefinition eller accessor för en instansegenskap, indexerare eller händelse som innehåller readonly modifieraren har följande begränsningar:

  • Parametern this är en ref readonly referens.
  • Medlemmen får inte omtilldela värdet för this mottagarens instansfält eller instansfält.
  • Medlemmen får inte tilldela om värdet för en instansfältliknande händelse (§15.8.2) för mottagaren.
  • Om en readonly-medlem anropar en icke-readonly-medlem måste den struktur som anges av this kopieras för att använda en skrivbar referens för this argumentet.

Observera: Instansfält innehåller det dolda bakgrundsfält som används för automatiskt implementerade egenskaper (§15.7.4). slutkommentar

Exempel: En readonly-medlem kan ändra tillståndet för ett objekt som refereras till av ett instansfält, även om den skrivskyddade medlemmen inte kan omtilldela instansmedlemmen. Följande kod visar omtilldelning och ändring av ett instansfält:

public struct S
{
    private List<string> messages;

    public S(IEnumerable<string> messages) =>
        this.messages = new List<string>(messages);

    public void InitializeMessages() =>
        messages = new List<string>();

    public readonly void AddMessage(string message)
    {
        if (messages == null)
        {
            throw new InvalidOperationException("Messages collection is not initialized.");
        }
        messages.Add(message);
    }
}

Metoden readonlyAddMessage kan ändra status för en meddelandelista. Medlemmen InitializeMessages kan rensa och initiera listan över meddelanden igen. När det gäller AddMessagereadonly är modifieraren giltig. När det gäller InitializeMessagesär det ogiltigt att lägga till readonly modifieraren. slutexempel

16.4 Syntetiserade post struct medlemmar

16.4.1 Allmänt

När det gäller en post-struct syntetiseras medlemmar såvida inte en medlem med en "matchande" signatur deklareras i record_struct_body eller en tillgänglig konkret icke-virtuell medlem med en "matchande" signatur ärvs. Två medlemmar anses matcha om de har samma signatur eller skulle betraktas som "gömmer sig" i ett arvsscenario. (Se Signaturer och överlagring §7.6.)

De syntetiserade medlemmarna beskrivs i följande underfunktioner.

16.4.2 Jämställdhetsmedlemmar

De syntetiserade likhetsmedlemmarna liknar dem för en postklass (§15.16.2), förutom bristen på EqualityContract, null-kontroller eller arv.

En post struct R implementerar System.IEquatable<R> och innehåller en syntetiserad starkt typbeskriven överlagring av Equals(R other), som är offentlig, enligt följande:

public readonly bool Equals(R other);

Den här metoden kan deklareras explicit. Det är dock ett fel om den explicita deklarationen inte matchar den förväntade signaturen eller tillgängligheten.

Om Equals(R other) är användardefinierad (dvs. inte syntetiseras) men GetHashCode inte är det, ska en varning utfärdas.

Den syntetiserade ska returnera true om och endast om för varje instansfält fieldN i posten struct värdet för System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN), där TN är fälttypen, är true.Equals(R)

Poststructen innehåller syntetiserade == operatorer och != operatorer som motsvarar operatorer som deklareras enligt följande:

public static bool operator==(R r1, R r2) => r1.Equals(r2);
public static bool operator!=(R r1, R r2) => !(r1 == r2);

Metoden Equals som anropas av operatorn == är den Equals(R other) metod som anges ovan. Operatorn != delegerar till operatorn == . Det är ett fel om operatorerna deklareras explicit.

Poststructen innehåller en syntetiserad åsidosättning som motsvarar en metod som deklareras enligt följande:

public override readonly bool Equals(object? obj);

Det är ett fel om åsidosättningen deklareras explicit. Den syntetiserade åsidosättningen ska returnera other is R temp && Equals(temp) var R är poststructen.

Poststructen innehåller en syntetiserad åsidosättning som motsvarar en metod som deklareras enligt följande:

public override readonly int GetHashCode();

Den här metoden kan deklareras explicit.

En varning ska rapporteras om en av Equals(R) och GetHashCode() uttryckligen deklareras men den andra metoden inte är det.

Den syntetiserade åsidosättningen av GetHashCode() ska returnera ett int resultat av att kombinera värdena System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN) för för varje instansfält fieldN med TN att vara typen av fieldN.

Exempel: Överväg följande poststruct:

record struct R1(T1 P1, T2 P2);

För detta skulle de syntetiserade likhetsmedlemmarna vara ungefär så här:

struct R1 : IEquatable<R1>
{
    public T1 P1 { get; set; }
    public T2 P2 { get; set; }
    public override bool Equals(object? obj) => obj is R1 temp && Equals(temp);
    public bool Equals(R1 other)
    {
        return
            EqualityComparer<T1>.Default.Equals(P1, other.P1) &&
            EqualityComparer<T2>.Default.Equals(P2, other.P2);
    }
    public static bool operator==(R1 r1, R1 r2) => r1.Equals(r2);
    public static bool operator!=(R1 r1, R1 r2) => !(r1 == r2);    
    public override int GetHashCode()
    {
        return HashCode.Combine(
            EqualityComparer<T1>.Default.GetHashCode(P1),
            EqualityComparer<T2>.Default.GetHashCode(P2));

slutexempel

16.4.3 Skriva ut medlemmar

En post struct innehåller en syntetiserad metod som motsvarar följande:

private bool PrintMembers(System.Text.StringBuilder builder);

Den här metoden utför följande uppgifter:

  1. För var och en av post structens utskrivbara medlemmar (icke-statiskt offentligt fält och läsbara egenskapsmedlemmar) lägger till medlemmens namn följt av "=" följt av medlemmens värde avgränsat med ", “,
  2. Returnerar sant om post structen har utskrivbara medlemmar.

För en medlem som har en värdetyp ska dess värde konverteras till en strängrepresentation.

Om postens utskrivbara medlemmar inte innehåller en läsbar egenskap med en icke-accessorreadonlyget är readonlyden syntetiserade PrintMembers . Det finns inget krav på att postens fält ska vara readonly för PrintMembers att metoden ska vara readonly.

Metoden PrintMembers kan deklareras explicit. Det är dock ett fel om den explicita deklarationen inte matchar den förväntade signaturen eller tillgängligheten.

Poststructen innehåller en syntetiserad metod som motsvarar följande:

public override string ToString();

Om post struct-metoden PrintMembers är readonlyska den syntetiserade ToString() metoden vara readonly.

Den här metoden kan deklareras explicit. Det är ett fel om den explicita deklarationen inte matchar den förväntade signaturen eller tillgängligheten.

Den här metoden utför följande uppgifter:

  1. Skapar en StringBuilder instans,
  2. Lägger till post struct-namnet i byggaren följt av "{",
  3. Anropar post struct-metoden PrintMembers som ger den byggaren, följt av " " om den returnerade true,
  4. Tillägg "}",
  5. Returnerar byggverktygets innehåll med builder.ToString().

Exempel: Överväg följande poststruct:

record struct R1(T1 P1, T2 P2);

För den här post structen skulle de syntetiserade utskriftsmedlemmarna vara ungefär så här:

struct R1 : IEquatable<R1>
{
    public T1 P1 { get; set; }
    public T2 P2 { get; set; }

    private bool PrintMembers(StringBuilder builder)
    {
        builder.Append(nameof(P1));
        builder.Append(" = ");
        builder.Append(this.P1); // or builder.Append(this.P1.ToString());
                                 // if P1 has a value type
        builder.Append(", ");

        builder.Append(nameof(P2));
        builder.Append(" = ");
        builder.Append(this.P2); // or builder.Append(this.P2.ToString());
                                 // if P2 has a value type

        return true;
    }

    public override string ToString()
    {
        var builder = new StringBuilder();
        builder.Append(nameof(R1));
        builder.Append(" { ");

        if (PrintMembers(builder))
            builder.Append(" ");

        builder.Append("}");
        return builder.ToString();
    }
}

slutexempel

16.4.4 Positionella post struct medlemmar

16.4.4.1 Allmänt

Förutom att tillhandahålla de medlemmar som beskrivs i föregående underbeskrivningar, syntetiserar positionella post structs (§16.2.1) ytterligare medlemmar med samma villkor som de andra medlemmarna, enligt beskrivningen i följande underbeskrivningar.

16.4.4.2 Primär konstruktor

En post struct har en offentlig konstruktor vars signatur motsvarar värdeparametrarna i typdeklarationen. Detta kallas den primära konstruktorn för typen. Det är ett fel att ha en primär konstruktor och en konstruktor med samma signatur som redan finns i struct. Om typdeklarationen inte innehåller någon delimited_parameter_list genereras ingen primär konstruktor.

record struct R1
{
    public R1() { } // OK
}

record struct R2()
{
    public R2() { } // error: 'R2' already defines
                    // a constructor with the same parameter types
}

Instansfältdeklarationer för en post struct tillåts inkludera variabelinitierare. Om det inte finns någon primär konstruktor körs instansinitierarna som en del av den parameterlösa konstruktorn. I annat fall kör den primära konstruktorn vid körning instansinitierarna som visas i post-struct-body.

Om en poststruct har en primär konstruktor ska alla användardefinierade konstruktorer ha en explicit this konstruktorinitierare som anropar den primära konstruktorn eller en uttryckligen deklarerad konstruktor.

Parametrar för den primära konstruktorn samt medlemmar i post structen finns i omfånget inom initierare av instansfält eller egenskaper. Instansmedlemmar skulle vara ett fel på dessa platser, men parametrarna för den primära konstruktorn skulle vara i omfång och användbara och skulle skugga medlemmar. Statiska medlemmar skulle också vara användbara.

En varning ska utfärdas om en parameter för den primära konstruktorn inte läss.

De slutgiltiga tilldelningsreglerna för struct-instanskonstruktorer gäller för den primära konstruktorn för poststrukturer. Följande är till exempel ett fel:

record struct Pos(int X) // def assignment error in primary constructor
{
    private int x;
    public int X {
        get { return x; } set { x = value; } 
    } = X;
}

16.4.4.3 Egenskaper

För varje parameter i en delimited_parameter_list som har samma namn och typ som ett explicit deklarerat instansfält gäller inte resten av den här underklienten.

För varje post struct-parameter för en delimited_parameter_list finns det en motsvarande medlem av den offentliga egenskapen vars namn och typ hämtas från värdeparameterdeklarationen.

För en post struct:

  • en offentlig get och init automatisk egenskap skapas om post struct har en readonly modifierare, get och set annars. Båda typerna av uppsättningsåtkomster (set och init) anses vara "matchande". Användaren kan därför deklarera en init-only-egenskap i stället för en syntetiserad föränderlig egenskap.

  • En ärvd abstract egenskap med matchande typ åsidosättas.

  • Ingen automatisk egenskap skapas om post structen har ett instansfält med förväntat namn och typ.

  • Det är ett fel om den ärvda egenskapen inte har publicget och set/init accessorer.

  • Det är ett fel om den ärvda egenskapen eller fältet är dolt.

  • Den automatiska egenskapen initieras till värdet för motsvarande primära konstruktorparameter.

  • Attribut kan tillämpas på den syntetiserade autoegenskapen och dess bakgrundsfält med hjälp property: av eller field: mål för attribut som syntaktiskt tillämpas på motsvarande post struct-parameter.

16.4.4.4 Dekonstruktion

En positionell post struct med minst en parameter syntetiserar en offentlig void-returning-instansmetod som anropas Deconstruct med en utparameterdeklaration för varje parameter i den primära konstruktordeklarationen. Varje parameter i Deconstruct har samma typ som motsvarande parameter för den primära konstruktordeklarationen. Metodens brödtext tilldelar varje parameter i metoden Deconstruct värdet från en instansmedlemsåtkomst till en medlem med samma namn. Om de instansmedlemmar som används i brödtexten inte innehåller en egenskap med en icke-accessorreadonlyget är readonlyden syntetiserade Deconstruct metoden . Metoden kan deklareras explicit. Det är ett fel om den explicita deklarationen inte matchar den förväntade signaturen eller tillgängligheten, eller om den är statisk.

16.5 Skillnader i klass och struct

16.5.1 Allmänt

Structs skiljer sig från klasser på flera viktiga sätt:

  • Structs är värdetyper (§16.5.2).
  • Alla structtyper ärver implicit från klassen System.ValueType (§16.5.3).
  • Tilldelning till en variabel av en structtyp skapar en kopia av värdet som tilldelas (§16.5.4).
  • Standardvärdet för en struct är det värde som genereras genom att ange alla fält till standardvärdet (§16.5.5).
  • Boxnings- och avboxningsåtgärder används för att konvertera mellan en structtyp och vissa referenstyper (§16.5.6).
  • Det menande av this är olikt inom structmedlemmar (§16.5.7).
  • En struct får inte deklarera en finalizer.
  • Händelsedeklarationer, egenskapsdeklarationer, egenskapsåtkomster, indexeringsdeklarationer och metoddeklarationer tillåts ha modifieraren readonly , medan det vanligtvis inte är tillåtet för samma medlemstyper i klasser.

16.5.2 Värdesemantik

Structs är värdetyper (§8.3) och sägs ha värdesemantik. Klasser är däremot referenstyper (§8.2) och sägs ha referenssemantik.

En variabel av en structtyp innehåller direkt data för structen, medan en variabel av en klasstyp innehåller en referens till ett objekt som innehåller data. När en struct B innehåller ett instansfält av typen A, och A är en structtyp, är det ett kompileringsfel för A att bero på B eller på en typ som konstruerats från B. En struct-Xberor direkt på en struct-Y om X innehåller ett instansfält av typen Y. Med den här definitionen är den fullständiga uppsättningen structs som en struct är beroende av den transitiva slutningen av relationen för direkt beroende av.

Exempel:

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

är ett fel eftersom Node innehåller ett instansfält av sin egen typ. Ett annat exempel

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

är ett fel eftersom var och en av typerna A, Boch C är beroende av varandra.

slutexempel

Med klasser är det möjligt för två variabler att referera till samma objekt och därmed möjligt för åtgärder på en variabel att påverka objektet som refereras av den andra variabeln. Med structs har variablerna var och en sin egen kopia av data (förutom när det gäller bireferensparametrar), och det är inte möjligt för åtgärder på den ena att påverka den andra. Förutom när det uttryckligen är nullbart (§8.3.12) är det inte möjligt för värden av en structtyp att vara null.

Obs! Om en struct innehåller ett fält av referenstyp kan innehållet i det refererade objektet ändras av andra åtgärder. Men värdet för själva fältet, dvs. vilket objekt det refererar till, kan inte ändras genom en mutation av ett annat structvärde. slutkommentar

Exempel: Med tanke på följande

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

Outputen är 10. Tilldelningen av a till b skapar en kopia av värdet och b påverkas därför inte av tilldelningen till a.x. Om Point istället hade deklarerats som en klass, skulle utdata vara 100 därför att a och b skulle referera till samma objekt.

slutexempel

16.5.3 Arv

Alla structtyper ärver implicit från klassen System.ValueType, som i sin tur ärver från klassen object. En struct-deklaration kan ange en lista över implementerade gränssnitt, men det är inte möjligt för en struct-deklaration att ange en basklass.

Struct-typer är aldrig abstrakta och är alltid implicit förseglade. Modifierarna abstract och sealed tillåts därför inte i en struct-deklaration.

Eftersom arv inte stöds för structs kan den deklarerade tillgängligheten för en struct-medlem inte vara protected, private protectedeller protected internal.

Funktionsmedlemmar i en struct kan inte vara abstrakta eller virtuella, och override modifieraren får endast åsidosätta metoder som ärvts från System.ValueType.

16.5.4 Tilldelning

Tilldelning till en variabel av en structtyp skapar en kopia av värdet som tilldelas. Detta skiljer sig från tilldelning till en variabel av en klasstyp, som kopierar referensen men inte det objekt som identifieras av referensen.

Precis som vid en tilldelning skapas en kopia av structen när en struct skickas som en värdeparameter eller returneras som resultat av en funktionsmedlem. En struct kan skickas med referens till en funktionsmedlem med hjälp av en bireferensparameter.

När en egenskap eller indexerare för en struct är målet för en tilldelning ska instansuttrycket som är associerat med egenskapen eller indexerarens åtkomst klassificeras som en variabel. Om instansuttrycket klassificeras som ett värde uppstår ett kompileringsfel. Detta beskrivs närmare i §12.24.2.

16.5.5 Standardvärden

Enligt beskrivningen i §9.3 initieras flera typer av variabler automatiskt till standardvärdet när de skapas. För variabler av klasstyper och andra referenstyper är nulldet här standardvärdet . Eftersom structs är värdetyper som inte kan vara null, är standardvärdet för en struct det värde som genereras genom att ange alla värdetypsfält till standardvärdet och alla referenstypfält till null.

Exempel: Med hänvisning till structen Point som deklarerats ovan, exemplet

Point[] a = new Point[100];

Initierar varje Point i fältet till det värde som skapas genom att fälten x och y sätts till noll.

slutexempel

Standardvärdet för en struct motsvarar det värde som returneras av standardkonstruktorn för structen (§8.3.3). När en struct inte deklarerar en explicit parameterlös instanskonstruktor syntetiseras standardkonstruktorn och returnerar alltid det värde som resulterar från att ange alla fält till deras standardvärden. Uttrycket default genererar alltid det nollinitierade standardvärdet, även när en struct deklarerar en explicit parameterlös instanskonstruktor (§16.4.9).

Obs! Structs bör utformas för att betrakta standardinitieringstillståndet som ett giltigt tillstånd. I exemplet

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

den användardefinierade instanskonstruktorn skyddar endast mot null värden där den uttryckligen anropas. I de fall där en KeyValuePair variabel är föremål för standardvärdeinitiering blir keyfälten value och null , och structen bör förberedas för att hantera det här tillståndet.

slutkommentar

16.5.6 Boxning och avboxning

Ett värde av en klasstyp kan konverteras till typ object eller till en gränssnittstyp som implementeras av klassen genom att bara behandla referensen som en annan typ vid kompileringstid. På samma sätt kan ett värde av typen object eller ett värde av en gränssnittstyp konverteras tillbaka till en klasstyp utan att referensen ändras (men naturligtvis krävs en körningstypskontroll i det här fallet).

Eftersom structs inte är referenstyper implementeras dessa åtgärder på olika sätt för structtyper. När ett värde av en structtyp konverteras till vissa referenstyper (enligt definitionen i §10.2.9), sker en boxningsåtgärd. På samma sätt, när ett värde av vissa referenstyper (enligt definitionen i §10.3.7) konverteras tillbaka till en structtyp, sker en avboxningsåtgärd. En viktig skillnad jämfört med samma åtgärder för klasstyper är att boxning och avboxning kopierar structvärdet antingen till eller från den boxade instansen.

Obs! Efter en boxnings- eller avboxningsåtgärd återspeglas inte ändringar som görs i den oboxade struct i rutan struct. slutkommentar

Mer information om boxning och unboxing finns i §10.2.9 och §10.3.7.

16.5.7 Innebörden av detta

Innebörden av this i en struct skiljer sig från innebörden av this i en klass, enligt beskrivningen i §12.8.14. När en struct-typ åsidosätter en virtuell metod som ärvts från System.ValueType (till exempel Equals, GetHashCodeeller ToString), orsakar anrop av den virtuella metoden via en instans av struct-typen inte boxning. Detta gäller även när struct används som en typparameter och anropet sker via en instans av typparametertypen.

Exempel:

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

Programmets utdata är:

1
2
3

Även om det är osnyggt för ToString att medföra bieffekter, visar exemplet att ingen boxning inträffade vid de tre anropen av x.ToString().

slutexempel

På samma sätt sker boxning aldrig implicit vid åtkomst till en medlem på en begränsad typparameter när medlemmen implementeras inom värdetypen. Anta till exempel att ett gränssnitt ICounter innehåller en metod Increment, som kan användas för att ändra ett värde. Om ICounter används som en begränsning anropas implementeringen av Increment metoden med en referens till variabeln som Increment anropades, aldrig en rutad kopia.

Exempel:

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

Det första anropet till Increment modifierar värdet i variabeln x. Detta motsvarar inte det andra anropet till Increment, som ändrar värdet i en rutad kopia av x. Därför är programmets utdata:

0
1
1

slutexempel

16.5.8 Fältinitierare

Enligt beskrivningen i §16.5.5 består standardvärdet för en struct av det värde som är resultatet av att ange alla värdetypsfält till deras standardvärde och alla referenstypfält till null. Statiska fält och instansfält i en struct tillåts inkludera variabelinitierare. När det gäller en instansfältinitierare ska dock minst en instanskonstruktor också deklareras, eller för en poststruct ska en delimited_parameter_list finnas.

Exempel:

Console.WriteLine($"Point is {new Point()}");

struct Point
{
    public int x = 1;
    public int y = 1;

    public Point() { }

    public override string ToString()
    {
        return "(" + x + ", " + y + ")";
    }
}
Point is (1, 1)

slutexempel

När en struct-instanskonstruktor inte har någon konstruktorinitierare utför konstruktorn implicit de initieringar som anges av variable_initializeri instansfälten som deklareras i dess struct. Detta motsvarar en sekvens med tilldelningar som körs omedelbart vid inmatning till konstruktorn.

När en struct-instanskonstruktor har en this() konstruktorinitierare som representerar den standardparameterlösa konstruktorn rensar den deklarerade konstruktorn implicit alla instansfält och utför de initieringar som anges av variable_initializeri instansfälten som deklareras i dess struct. Omedelbart efter inmatningen till konstruktorn anges alla värdetypsfält till standardvärdet och alla referenstypfält är inställda på null. Omedelbart efter det körs en sekvens med tilldelningar som motsvarar variable_initializers.

En field_declaration som deklareras direkt inom en struct_declaration som har struct_modifierreadonly skall ha field_modifierreadonly.

16.5.9 Konstruktorer

En struct kan deklarera instanskonstruktorer med noll eller fler parametrar. Om en struct inte har någon uttryckligen deklarerad parameterlös instanskonstruktor syntetiseras en med offentlig tillgänglighet, som alltid returnerar det värde som är resultatet av att ange alla värdetypsfält till standardvärdet och alla referenstypfält till null (§8.3.3). I sådana fall ignoreras alla instansfältinitierare när konstruktorn körs.

En uttryckligen deklarerad parameterlös instanskonstruktor ska ha offentlig tillgänglighet.

Exempel: Givet följande:

using System;
struct Point
{
    int x = -1, y = -2;

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

    public override string ToString()
    {
        return "(" + x + ", " + y + ")";
    }
}

class A
{
    static void Main()
    {
        Console.WriteLine($"Point is {new Point()}");
        Console.WriteLine($"Point is {new Point(0,0)}");
    }
}
Point is (0, 0)
Point is (0, 0)

-uttrycken både skapar en Point med och y initieras till noll, vilket i fallet med x anropet till den parameterlösa instanskonstruktorn kan vara överraskande, eftersom båda instansfälten har initialiserare, men de körs inte.

slutexempel

En struct-instanskonstruktor får inte innehålla en konstruktorinitierare av formuläret base(argument_list), där argument_list är valfritt. Utförandet av en instanskonstruktor får inte leda till att en konstruktor körs i structens bastyp System.ValueType.

Parametern this för en struct-instanskonstruktor motsvarar en utdataparameter av typen struct. Således ska this tilldelas definitivt (§9.4) på varje plats där konstruktorn returnerar. På samma sätt kan den inte läsas (även implicit) i konstruktorns brödtext innan den definitivt tilldelas.

Om struct-instanskonstruktorn anger en konstruktorinitierare anses initieraren vara en bestämd tilldelning till detta som inträffar före konstruktorns brödtext. Därför har själva kroppen inga initieringskrav.

Instansfält (förutom fixed fält) ska definitivt tilldelas i struct-instanskonstruktorer som inte har någon this() initialiserare.

Exempel: Överväg implementeringen av instanskonstruktorn nedan:

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

Ingen instansfunktionsmedlem (inklusive uppsättningsåtkomsterna för egenskaperna X och Y) kan anropas förrän alla fält i den struct som skapas definitivt har tilldelats. Observera dock att om Point det var en klass i stället för en struct skulle instanskonstruktorimplementeringen tillåtas. Det finns ett undantag till detta och det omfattar automatiskt implementerade egenskaper (§15.7.4). De bestämda tilldelningsreglerna (§12.24.2) undantar specifikt tilldelning till en autoegenskap av en structtyp inom en instanskonstruktor av den typen: en sådan tilldelning anses vara en bestämd tilldelning av det dolda bakgrundsfältet för den automatiska egenskapen. Därför tillåts följande:

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

slutexempel]

16.5.10 Statiska konstruktorer

Statiska konstruktorer för structs följer de flesta av samma regler som för klasser. Körningen av en statisk konstruktor för en struct-typ utlöses av den första av följande händelser som inträffar inom en programdomän:

  • En statisk medlem av structtypen refereras till.
  • En explicit deklarerad konstruktor av typen struct anropas.

Obs! Skapandet av standardvärden (§16.5.5) av structtyper utlöser inte den statiska konstruktorn. (Ett exempel på detta är det initiala värdet för element i en matris.) slutkommentar

16.5.11 Egenskaper

En property_declaration (§15.7.1) för en instansegenskap i en struct_declaration kan innehålla property_modifierreadonly. En statisk egenskap får dock inte innehålla den modifieraren.

Det är ett fel vid kompileringsfasen att försöka ändra tillståndet för en instansvariabel av typen struct via en skrivskyddad egenskap som deklarerats i den typen.

Det är ett fel vid kompilering för en automatiskt implementerad egenskap som har en readonly modifier, att också ha en set accessor.

Det är ett kompileringsfel för en automatiskt implementerad egenskap i en readonly struktur som har en set accessor.

En automatiskt implementerad egenskap som deklareras i en readonly struktur behöver inte ha någon readonly modifierare, eftersom dess get accessor implicit antas vara skrivskyddad.

Det är ett kompileringsfel att ha en readonly modifierare på själva egenskapen samt på någon av dess get och set accessorer.

Det är ett kompileringsfel om en egenskap har en readonly-modifierare för alla sina accessorer.

Obs! Om du vill korrigera felet flyttar du modifieraren från åtkomstgivarna till själva egenskapen. slutkommentar

För ett egenskapsåtkomstuttryck: s.P

  • Det är ett kompileringsfel om s.P anropar den angivna accessorn M av typen T när processen i §12.6.6.1 skulle skapa en tillfällig kopia av s.
  • Om s.P anropar get-accessorn av typen Tföljs processen i §12.6.6.1 , inklusive att skapa en tillfällig kopia av s vid behov.

Automatiskt implementerade egenskaper (§15.7.4) använder dolda bakgrundsfält, som endast är tillgängliga för egenskapsåtkomsterna.

Obs! Den här åtkomstbegränsningen innebär att konstruktorer i structs som innehåller automatiskt implementerade egenskaper ofta behöver en explicit konstruktorinitierare där de annars inte skulle behöva en, för att uppfylla kravet på alla fält som definitivt tilldelas innan någon funktionsmedlem anropas eller konstruktorn returnerar. slutkommentar

16.5.12 Metoder

En method_declaration (§15.6.1) för en instansmetod i en struct_declaration kan innehålla method_modifierreadonly. En statisk metod får dock inte innehålla den modifieraren.

Det är ett kompileringsfel att försöka ändra tillståndet för en instans struct-variabel via en skrivskyddad metod som deklarerats i den structen.

Även om en skrivskyddad metod kan anropa en syskonmetod som inte är skrivskyddad eller en egenskap eller indexerare, skapas en implicit kopia av this som en försiktighetsåtgärd.

En skrivskyddad metod kan anropa en syskonegenskap eller en indexerare som är skrivskyddad. Om en syskonmedlems accessor inte är explicit eller implicit skrivskyddad uppstår ett kompileringsfel.

Alla method_declarationav en partiell metod ska ha en readonly modifierare, eller så får ingen av dem ha den.

16.5.13 Indexerare

En indexer_declaration (§15.9) för en instansindexerare i en struct_declaration kan innehålla indexer_modifierreadonly.

Det är ett kompileringsfel att försöka ändra tillståndet för en instans struct-variabel via en skrivskyddad indexerare som deklarerats i den structen.

Det är ett kompileringsfel att ha en readonly modifierare på en indexerare själv samt på någon av dess get eller set accessorer.

Det är ett kompileringsfel för en indexerare att ha en skrivskyddad modifierare på alla dess åtkomstorer.

Obs! Om du vill korrigera felet flyttar du modifieraren från åtkomstgivarna till själva indexeraren. slutkommentar

16.5.14 Händelser

En event_declaration (§15.8.1) för en instanshändelse som inte är fältliknande i en struct_declaration kan innehålla event_modifierreadonly. En statisk händelse får dock inte innehålla den modifieraren.

16.5.15 Villkor för säker kontext

16.5.15.1 Allmänt

Vid kompilering är varje uttryck associerat med en kontext där den instansen och alla dess fält kan nås på ett säkert sätt, dess säkra kontext. Safe-context är en kontext som omsluter ett uttryck, vilket är säkert för värdet att fly till.

Alla uttryck vars kompileringstidstyp inte är en referens-struct har en säker kontext av anroparkontext.

Ett default uttryck för alla typer har safe-context för anroparkontext.

För varje icke-standarduttryck vars kompileringstidstyp är en ref struct, har en säker kontext definierats av följande avsnitt.

Safe-context registrerar vilken kontext ett värde kan kopieras till. Med en tilldelning från ett uttryck E1 med en säker kontext S1, till ett uttryck E2 med en säker kontext S2, är det ett fel om S2 är en större kontext än S1.

Det finns tre olika safe-context-värden, samma som referens-safe-context-värdena som definierats för referensvariabler (§9.7.2): declaration-block, function-member och caller-context. Ett uttrycks säkra kontext begränsar dess användning enligt följande:

  • För ett returuttalande return e1 ska säkerhetskontexten för e1 vara anroparkontext.
  • För en tilldelning e1 = e2 ska säkerhetskontexten e2 vara minst lika bred som säkerhetskontexten e1.

För ett metodanrop, om det finns ett ref eller ett out-argument av en ref struct-typ (inklusive mottagaren om inte typen är readonly), med safe-context S1, får inget argument (inklusive mottagaren) ha en snävare säker kontext än S1.

16.5.15.2 Parametersäker kontext

En parameter av typen ref struct, inklusive parametern this för en instansmetod, har en säker kontext av anroparkontext.

16.5.15.3 Säker kontext för lokal variabel

En lokal variabel av typen ref struct har en säker kontext enligt följande:

  • Om variabeln är en iterationsvariabel för en foreach loop är variabelns safe-context samma som safe-context för loopens foreach uttryck.
  • Om variabelns deklaration annars har en initiering är variabelns safe-context samma som initiatorns säkra kontext.
  • I annat fall är variabeln oinitialiserad vid deklarationspunkten och har en säkert kontext från anroparens kontext.

16.5.15.4 Fältsäker kontext

En referens till ett fält e.F, där typen av F är en ref struct-typ, har en säkerhetskontext som är densamma som säkerhetskontexten för e.

16.5.15.5 Operatorer

Tillämpningen av en användardefinierad operator behandlas som ett metodanrop (§16.5.15.6).

För en operator som ger ett värde, till exempel e1 + e2 eller c ? e1 : e2, är den säkra kontexten för resultatet den smalaste kontexten bland de säkra kontexterna hos operatorns operander. För en unary-operator som ger ett värde, till exempel +e, är resultatets säkra kontext därför operandens säkra kontext.

Obs! Den första operanden för en villkorsoperator är en bool, så dess safe-context är caller-context. Av detta följer att den resulterande säkra kontexten är den smalaste säkra kontexten för den andra och tredje operanden. slutkommentar

16.5.15.6 Metod- och egenskapsanrop

Ett värde som härrör från en metodanrop e1.M(e2, ...) eller egenskapsanrop e.P har en säker kontext för den minsta av följande kontexter:

  • anroparkontext.
  • Den säkra kontexten för alla argumentuttryck (inklusive mottagaren).

Ett egenskapsanrop (antingen get eller set) behandlas som en metodanrop av den underliggande metoden av ovanstående regler.

16.5.15.7 stackalloc

Resultatet av ett stackalloc-uttryck har safe-context för function-member.

16.5.15.8 Konstruktoranrop

Ett new uttryck som anropar en konstruktor följer samma regler som en metodanrop som anses returnera den typ som skapas.

Dessutom är safe-context den minsta av de säkra kontexterna för alla argument och operander för alla objektinitieringsuttryck, rekursivt, om någon initiator finns.

Obs! Dessa regler förlitar sig på att Span<T> inte har en konstruktor i följande form:

public Span<T>(ref T p)

En sådan konstruktor gör instanser av Span<T> som används som fält oskiljbara från ett ref fält. Säkerhetsreglerna som beskrivs i det här dokumentet beror på att fälten ref inte är en giltig konstruktion i C# eller .NET. slutkommentar