Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
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
refmodifieraren. - 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 structtyp får inte boxas tillSystem.ValueTypeellerSystem.Object. - En
ref structtyp får inte deklareras för att implementera något gränssnitt. - En instansmetod som deklareras i
objecteller iSystem.ValueTypemen som inte åsidosätts i enref structtyp ska inte anropas med en mottagare av denref structtypen.
Obs! En
ref structfår inte deklareraasyncinstansmetoder eller använda enyield return-yield breaksats i en instansmetod, eftersom den implicitathis-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 enref readonlyreferens. - Medlemmen får inte omtilldela värdet för
thismottagarens 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
thiskopieras för att använda en skrivbar referens förthisargumentet.
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
readonlyAddMessagekan ändra status för en meddelandelista. MedlemmenInitializeMessageskan rensa och initiera listan över meddelanden igen. När det gällerAddMessagereadonlyär modifieraren giltig. När det gällerInitializeMessagesär det ogiltigt att lägga tillreadonlymodifieraren. 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:
- 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 ", “, - 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:
- Skapar en
StringBuilderinstans, - Lägger till post struct-namnet i byggaren följt av "
{", - Anropar post struct-metoden
PrintMemberssom ger den byggaren, följt av "" om den returnerade true, - Tillägg "
}", - 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
getochinitautomatisk egenskap skapas om post struct har enreadonlymodifierare,getochsetannars. Båda typerna av uppsättningsåtkomster (setochinit) 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
abstractegenskap 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
publicgetochset/initaccessorer.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 ellerfield: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
Nodeinnehåller ett instansfält av sin egen typ. Ett annat exempelstruct A { B b; } struct B { C c; } struct C { A a; }är ett fel eftersom var och en av typerna
A,BochCä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 avatillbskapar en kopia av värdet ochbpåverkas därför inte av tilldelningen tilla.x. OmPointistället hade deklarerats som en klass, skulle utdata vara100därför attaochbskulle 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
Pointsom deklarerats ovan, exempletPoint[] a = new Point[100];Initierar varje
Pointi fältet till det värde som skapas genom att fältenxochysä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
nullvärden där den uttryckligen anropas. I de fall där enKeyValuePairvariabel är föremål för standardvärdeinitiering blirkeyfältenvalueochnull, 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
structi rutanstruct. 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
ToStringatt medföra bieffekter, visar exemplet att ingen boxning inträffade vid de tre anropen avx.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
Incrementmodifierar värdet i variabelnx. Detta motsvarar inte det andra anropet tillIncrement, som ändrar värdet i en rutad kopia avx. Därför är programmets utdata:0 1 1slutexempel
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
Pointmed ochyinitieras till noll, vilket i fallet medxanropet 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
XochY) kan anropas förrän alla fält i den struct som skapas definitivt har tilldelats. Observera dock att omPointdet 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.Panropar den angivna accessornMav typenTnär processen i §12.6.6.1 skulle skapa en tillfällig kopia avs. - Om
s.Panropar get-accessorn av typenTföljs processen i §12.6.6.1 , inklusive att skapa en tillfällig kopia avsvid 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 e1ska säkerhetskontexten före1vara anroparkontext. - För en tilldelning
e1 = e2ska säkerhetskontextene2vara minst lika bred som säkerhetskontextene1.
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
foreachloop är variabelns safe-context samma som safe-context för loopensforeachuttryck. - 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 ettreffält. Säkerhetsreglerna som beskrivs i det här dokumentet beror på att fältenrefinte är en giltig konstruktion i C# eller .NET. slutkommentar
ECMA C# draft specification