Anteckning
Å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.
15.1 Allmänt
En klass är en datastruktur som kan innehålla datamedlemmar (konstanter och fält), funktionsmedlemmar (metoder, egenskaper, händelser, indexerare, operatorer, instanskonstruktorer, finalizers och statiska konstruktorer) och kapslade typer. Klasstyper stöder arv, en mekanism där en härledd klass kan utöka och specialisera en basklass.
15.2 Klassdeklarationer
15.2.1 Allmänt
En class_declaration är en type_declaration (§14.7) som deklarerar en ny klass.
class_declaration
: attributes? class_modifier* 'partial'? 'class' identifier
type_parameter_list? class_base? type_parameter_constraints_clause*
class_body ';'?
;
En class_declaration består av en valfri uppsättning attribut (§22), följt av en valfri uppsättning class_modifier (§15.2.2), följt av en valfri partial
modifierare (§15.2.7), följt av nyckelordet class
och en identifierare som namnger klassen, följt av en valfri type_parameter_list (§15.2.3), följt av en valfri class_base specifikation (§15.2.4), följt av en valfri uppsättning type_parameter_constraints_clause (§15.2.5), följt av en class_body (§15.2.6), eventuellt följt av ett semikolon.
En klassdeklaration får inte tillhandahålla type_parameter_constraints_clause om den inte också tillhandahåller en type_parameter_list.
En klassdeklaration som tillhandahåller en type_parameter_list är en allmän klassdeklaration. Dessutom är alla klasser kapslade i en generisk klassdeklaration eller en generisk structdeklaration i sig en generisk klassdeklaration, eftersom typargument för den innehållande typen ska tillhandahållas för att skapa en konstruerad typ (§8.4).
15.2.2 Klassmodifierare
15.2.2.1 Allmänt
En class_declaration kan eventuellt innehålla en sekvens med klassmodifierare:
class_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'abstract'
| 'sealed'
| 'static'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).
Det är ett kompileringsfel för samma modifierare som ska visas flera gånger i en klassdeklaration.
Modifieraren new
tillåts i kapslade klasser. Den anger att klassen döljer en ärvd medlem med samma namn, enligt beskrivningen i §15.3.5. Det är ett kompileringsfel om new
-modifieraren visas i en klassdeklaration som inte är en nästad klassdeklaration.
Modifierarna public
, protected
, internal
och private
styr tillgängligheten för klassen. Beroende på i vilken kontext klassdeklarationen inträffar kanske vissa av dessa modifierare inte tillåts (§7.5.2).
När en partiell typdeklaration (§15.2.7) innehåller en tillgänglighetsspecifikation (via public
, protected
, internal
och private
modifierare), ska den specifikationen överensstämma med alla andra delar som innehåller en tillgänglighetsspecifikation. Om ingen del av en partiell typ innehåller en tillgänglighetsspecifikation ges typen lämplig standardtillgänglighet (§7.5.2).
Modifierarna abstract
, sealed
och static
diskuteras i följande delavsnitt.
15.2.2.2 Abstrakta klasser
Modifieraren abstract
används för att indikera att en klass är ofullständig och att den endast är avsedd att användas som basklass. En abstrakt klass skiljer sig från en icke-abstrakt klass på följande sätt:
- Det går inte att instansiera en abstrakt klass direkt, och det är ett kompileringsfel att använda operatorn i
new
en abstrakt klass. Även om det är möjligt att ha variabler och värden vars kompileringstidstyper är abstrakta, är sådana variabler och värden nödvändigtvis antingennull
eller innehåller referenser till instanser av icke-abstrakta klasser som härletts från abstrakta typer. - En abstrakt klass tillåts (men krävs inte) att innehålla abstrakta medlemmar.
- Det går inte att försegla en abstrakt klass.
När en icke-abstrakt klass härleds från en abstrakt klass ska den icke-abstrakta klassen innehålla faktiska implementeringar av alla ärvda abstrakta medlemmar, vilket åsidosätter dessa abstrakta medlemmar.
Exempel: I följande kod
abstract class A { public abstract void F(); } abstract class B : A { public void G() {} } class C : B { public override void F() { // Actual implementation of F } }
Den abstrakta klassen
A
introducerar en abstrakt metodF
. KlassenB
introducerar ytterligare en metodG
, men eftersom den inte tillhandahåller någon implementering avF
,B
ska den också förklaras abstrakt. KlassenC
åsidosätterF
och tillhandahåller en faktisk implementering. Eftersom det inte finns några abstrakta medlemmar iC
C
tillåts (men krävs inte) att vara icke-abstrakta.slutexempel
Om en eller flera delar av en partiell typdeklaration (§15.2.7) av en klass innehåller abstract
modifieraren är klassen abstrakt. Annars är klassen konkret.
15.2.2.3 Förseglade klasser
Modifieraren sealed
används för att förhindra härledning från en klass. Ett kompileringsfel uppstår om en förseglad klass anges som basklass för en annan klass.
En förseglad klass kan inte heller vara en abstrakt klass.
Obs! Modifieraren
sealed
används främst för att förhindra oavsiktlig härledning, men den möjliggör även vissa körningsoptimeringar. Eftersom en förseglad klass är känd för att aldrig ha några härledda klasser är det särskilt möjligt att omvandla anrop för virtuella funktionsmedlemmar på förseglade klassinstanser till icke-virtuella anrop. slutkommentar
Om en eller flera delar av en partiell typdeklaration (§15.2.7) av en klass inkluderar sealed
modifieraren, är klassen förseglad. Annars är klassen oförseglad.
15.2.2.4 Statiska klasser
15.2.2.4.1 Allmänt
Modifieraren static
används för att markera klassen som deklareras som en statisk klass. En statisk klass får inte instansieras, ska inte användas som en typ och får endast innehålla statiska medlemmar. Endast en statisk klass kan innehålla deklarationer av tilläggsmetoder (§15.6.10).
En statisk klassdeklaration omfattas av följande begränsningar:
- En statisk klass får inte innehålla en
sealed
ellerabstract
modifierare. (Eftersom en statisk klass inte kan instansieras eller härledas från fungerar den dock som om den var både förseglad och abstrakt.) - En statisk klass får inte innehålla en class_base specifikation (§15.2.4) och kan inte uttryckligen ange en basklass eller en lista över implementerade gränssnitt. En statisk klass ärver implicit från typen
object
. - En statisk klass får endast innehålla statiska medlemmar (§15.3.8).
Obs! Alla konstanter och kapslade typer klassificeras som statiska medlemmar. slutkommentar
- En statisk klass får inte ha medlemmar med
protected
,private protected
ellerprotected internal
deklarerad tillgänglighet.
Det är ett kompileringsfel att bryta mot någon av dessa begränsningar.
En statisk klass har inga instanskonstruktorer. Det går inte att deklarera en instanskonstruktor i en statisk klass och ingen standardinstanskonstruktor (§15.11.5) tillhandahålls för en statisk klass.
Medlemmarna i en statisk klass är inte automatiskt statiska och medlemsdeklarationerna ska uttryckligen innehålla en static
modifierare (förutom konstanter och kapslade typer). När en klass är kapslad i en statisk yttre klass är den kapslade klassen inte en statisk klass om den inte uttryckligen innehåller en static
modifierare.
Om en eller flera delar av en partiell typdeklaration (§15.2.7) av en klass inkluderar static
modifieraren, är klassen statisk. Annars är klassen inte statisk.
15.2.2.4.2 Referera till statiska klasstyper
En namespace_or_type_name (§7.8) får referera till en statisk klass om
- Namespace_or_type_name är
T
i ett Namespace_or_type_name i formuläretT.I
, eller - Det namespace_or_type-namn är
T
i en typeof_expression (§12.8.18) av formentypeof(T)
.
En primary_expression (§12.8) är tillåten att referera till en statisk klass om
- Den primary_expression är
E
i en member_access (§12.8.7) av formenE.I
.
I andra sammanhang är det ett kompileringsfel att referera till en statisk klass.
Obs! Det är till exempel ett fel för en statisk klass att användas som basklass, en komponenttyp (§15.3.7) av en medlem, ett allmänt typargument eller en typparameterbegränsning. På samma sätt kan inte en statisk klass användas i en matristyp, ett nytt uttryck, ett cast-uttryck, ett är-uttryck, ett som-uttryck, ett
sizeof
uttryck eller ett standardvärdeuttryck. slutkommentar
15.2.3 Typparametrar
En typparameter är en enkel identifierare som anger en platshållare för ett typargument som tillhandahålls för att skapa en konstruerad typ. Av constrast är ett typargument (§8.4.2) den typ som ersätts med typparametern när en konstruerad typ skapas.
type_parameter_list
: '<' decorated_type_parameter (',' decorated_type_parameter)* '>'
;
decorated_type_parameter
: attributes? type_parameter
;
type_parameter definieras i §8.5.
Varje typparameter i en klassdeklaration definierar ett namn i deklarationsutrymmet (§7.3) för den klassen. Därför kan den inte ha samma namn som en annan typparameter för den klassen eller en medlem som deklarerats i den klassen. En typparameter får inte ha samma namn som själva typen.
Två partiella allmänna typdeklarationer (i samma program) bidrar till samma obundna generiska typ om de har samma fullständigt kvalificerade namn (som innehåller en generic_dimension_specifier (§12.8.18) för antalet typparametrar) (§7.8.3). Två sådana partiella typdeklarationer ska ange samma namn för varje typparameter i ordning.
15.2.4 Klassbasspecifikation
15.2.4.1 Allmänt
En klassdeklaration kan innehålla en class_base specifikation, som definierar klassens direkta basklass och gränssnitten (§18) som implementeras direkt av klassen.
class_base
: ':' class_type
| ':' interface_type_list
| ':' class_type ',' interface_type_list
;
interface_type_list
: interface_type (',' interface_type)*
;
15.2.4.2 Basklasser
När en class_type ingår i class_base anger den den direkta basklassen för klassen som deklareras. Om en icke-partiell klassdeklaration inte har någon class_base, eller om class_base endast visar gränssnittstyper, antas den direkta basklassen vara object
. När en partiell klassdeklaration innehåller en basklassspecifikation ska basklassspecifikationen referera till samma typ som alla andra delar av den partiella typen som innehåller en basklassspecifikation. Om ingen del av en partiell klass innehåller en basklassspecifikation är object
basklassen . En klass ärver medlemmar från sin direkta basklass enligt beskrivningen i §15.3.4.
Exempel: I följande kod
class A {} class B : A {}
Klassen
A
sägs vara den direkta basklassen förB
, ochB
sägs härledas frånA
. EftersomA
inte uttryckligen anger en direkt basklass är dess direkta basklass implicitobject
.slutexempel
För en konstruerad klasstyp, inklusive en kapslad typ som deklareras i en allmän typdeklaration (§15.3.9.7), om en basklass anges i den generiska klassdeklarationen, erhålls basklassen för den konstruerade typen genom att ersätta, för varje type_parameter i basklassdeklarationen, motsvarande type_argument av den konstruerade typen.
Exempel: Givet de allmänna klassdeklarationerna
class B<U,V> {...} class G<T> : B<string,T[]> {...}
basklassen för den konstruerade typen
G<int>
skulle varaB<string,int[]>
.slutexempel
Basklassen som anges i en klassdeklaration kan vara en konstruerad klasstyp (§8.4). En basklass kan inte vara en typparameter på egen hand (§8.5), även om den kan omfatta de typparametrar som finns i omfånget.
Exempel:
class Base<T> {} // Valid, non-constructed class with constructed base class class Extend1 : Base<int> {} // Error, type parameter used as base class class Extend2<V> : V {} // Valid, type parameter used as type argument for base class class Extend3<V> : Base<V> {}
slutexempel
Den direkta basklassen för en klasstyp ska vara minst lika tillgänglig som själva klasstypen (§7.5.5). Det är till exempel ett kompileringsfel för en offentlig klass att härleda från en privat eller intern klass.
Den direkta basklassen System.Array
för en klasstyp får inte vara någon av följande typer: System.Delegate
, System.Enum
, System.ValueType
eller dynamic
typ. Dessutom ska en generisk klassdeklaration inte användas System.Attribute
som en direkt eller indirekt basklass (§22.2.1).
Vid fastställandet av innebörden av den direkta basklassspecifikationen A
för en klass B
antas den direkta basklassen B
för tillfälligt vara object
, vilket säkerställer att innebörden av en basklassspecifikation inte rekursivt kan vara beroende av sig själv.
Exempel: Följande
class X<T> { public class Y{} } class Z : X<Z.Y> {}
är i fel eftersom basklasspecifikationen
X<Z.Y>
anser att den direkta basklassen förZ
ärobject
, och därför (enligt reglerna i §7.8) ansesZ
inte ha en medlemY
.slutexempel
Basklasserna för en klass är den direkta basklassen och dess basklasser. Med andra ord är uppsättningen basklasser den transitiva avslutningen av den direkta basklassrelationen.
Exempel: I följande:
class A {...} class B<T> : A {...} class C<T> : B<IComparable<T>> {...} class D<T> : C<T[]> {...}
basklasserna
D<int>
för ärC<int[]>
,B<IComparable<int[]>>
,A
ochobject
.slutexempel
Förutom klass object
har varje klass exakt en direkt basklass. Klassen object
har ingen direkt basklass och är den ultimata basklassen för alla andra klasser.
Det är ett kompileringsfel för en klass att vara beroende av sig själv. I den här regeln är en klass direkt beroende av dess direkta basklass (om någon) och är direkt beroende av den närmaste omslutande klassen inom vilken den är kapslad (om någon). Med den här definitionen är den fullständiga mängden klasser som en viss klass är beroende av den transitiva slutningen av direkt beroende av-relationen.
Exempel: Exemplet
class A : A {}
är felaktig eftersom klassen är beroende av sig själv. Likaså exemplet
class A : B {} class B : C {} class C : A {}
är fel eftersom klasserna är cirkulärt beroende av sig själva. Slutligen exemplet
class A : B.C {} class B : A { public class C {} }
Resulterar i ett kompileringsfel eftersom A är beroende av
B.C
(dess direkta basklass), som i sin tur beror påB
(dess omedelbart omslutande klass), och denna klass cirkulärt beror påA
.slutexempel
En klass är inte beroende av de klasser som är kapslade i den.
Exempel: I följande kod
class A { class B : A {} }
B
beror påA
(eftersomA
är både dess direkta basklass och dess omedelbart omslutande klass), menA
är inte beroende avB
(eftersomB
är varken en basklass eller en omslutande klass avA
). Exemplet är därför giltigt.slutexempel
Det är inte möjligt att härleda från en sluten klass.
Exempel: I följande kod
sealed class A {} class B : A {} // Error, cannot derive from a sealed class
Klassen
B
har ett fel eftersom den försöker härleda från den förseglade klassenA
.slutexempel
15.2.4.3 Implementeringar av gränssnitt
En class_base specifikation kan innehålla en lista över gränssnittstyper, i vilket fall klassen sägs implementera de angivna gränssnittstyperna. För en konstruerad klasstyp, inklusive en kapslad typ som deklarerats i 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.
Gränssnittsuppsättningen för en typ som är deklarerad i flera delar (§15.2.7) är unionen av de gränssnitt som anges på varje del. En viss gränssnitt kan bara namnges en gång på varje del, men flera delar kan namnge samma basgränssnitt. Det får bara finnas en implementering av varje medlem i ett visst gränssnitt.
Exempel: I följande:
partial class C : IA, IB {...} partial class C : IC {...} partial class C : IA, IB {...}
uppsättningen med basgränssnitt för klassen
C
ärIA
,IB
ochIC
.slutexempel
Vanligtvis tillhandahåller varje del en implementering av de gränssnitt som deklareras på den delen; dock är detta inte ett krav. En del kan tillhandahålla implementeringen för ett gränssnitt som deklarerats på en annan del.
Exempel:
partial class X { int IComparable.CompareTo(object o) {...} } partial class X : IComparable { ... }
slutexempel
De basgränssnitt som anges i en klassdeklaration kan vara konstruerade gränssnittstyper (§8.4, §18.2). Ett basgränssnitt kan inte vara en typparameter på egen hand, även om det kan omfatta de typparametrar som finns i omfånget.
Exempel: Följande kod visar hur en klass kan implementera och utöka konstruerade typer:
class C<U, V> {} interface I1<V> {} class D : C<string, int>, I1<string> {} class E<T> : C<int, T>, I1<T> {}
slutexempel
Gränssnittsimplementeringar diskuteras ytterligare i §18.6.
15.2.5 Typparameterbegränsningar
Allmänna typ- och metoddeklarationer kan också ange typparameterbegränsningar genom att inkludera type_parameter_constraints_clauses.
type_parameter_constraints_clause
: 'where' type_parameter ':' type_parameter_constraints
;
type_parameter_constraints
: primary_constraint (',' secondary_constraints)? (',' constructor_constraint)?
| secondary_constraints (',' constructor_constraint)?
| constructor_constraint
;
primary_constraint
: class_type nullable_type_annotation?
| 'class' nullable_type_annotation?
| 'struct'
| 'notnull'
| 'unmanaged'
;
secondary_constraint
: interface_type nullable_type_annotation?
| type_parameter nullable_type_annotation?
;
secondary_constraints
: secondary_constraint (',' secondary_constraint)*
;
constructor_constraint
: 'new' '(' ')'
;
Varje type_parameter_constraints_clause består av token where
, följt av namnet på en typparameter, följt av ett kolon och en lista med begränsningar för den typparametern. Det kan finnas högst en where
sats för varje typparameter och satserna where
kan anges i valfri ordning. Precis som get
- och set
-tokens i en egenskapsåtkomst är where
-token inte ett nyckelord.
Listan över begränsningar som anges i en where
-sats kan innehålla någon av följande komponenter i den här ordningen: en enda primär begränsning, en eller flera sekundära begränsningar och konstruktorvillkoret. new()
En primär begränsning kan vara en klasstyp, referenstypsbegränsningenclass
, värdetypsbegränsningenstruct
, icke-null-begränsningennotnull
eller begränsningen för ohanterad typunmanaged
. Klasstypen och referenstypbegränsningen kan innehålla nullable_type_annotation.
En sekundär begränsning kan vara en interface_type eller type_parameter, eventuellt följt av en nullable_type_annotation. Förekomsten av nullable_type_annotation anger att typargumentet tillåts vara den nullbara referenstypen som motsvarar en referenstyp som inte är nullbar som uppfyller villkoret.
Begränsningen för referenstyp anger att ett typargument som används för typparametern ska vara en referenstyp. Alla klasstyper, gränssnittstyper, ombudstyper, matristyper och typparametrar som är kända för att vara en referenstyp (enligt definitionen nedan) uppfyller den här begränsningen.
Klasstypen, referenstypsbegränsningen och de sekundära begränsningarna kan innehålla nullable-typsanteckningen. Förekomsten eller frånvaron av den här kommentaren på typparametern anger förväntningarna på nullabilitet för typargumentet:
- Om villkoret inte innehåller den nullbara typanteckningen förväntas typargumentet vara en referenstyp som inte är nullbar. En kompilator kan utfärda en varning om typargumentet är en referenstyp som kan ogiltigförklaras.
- Om villkoret innehåller den nullbara typanteckningen, uppfylls villkoret av både en referenstyp som inte kan vara null och en referenstyp som kan vara null.
Ogiltigheten för typargumentet behöver inte matcha typparameterns nullabilitet. En kompilator kan utfärda en varning om nullbarheten för typparametern inte matchar typargumentets nullbarhet.
Obs! Om du vill ange att ett typargument är en nullbar referenstyp lägger du inte till anteckningen av typen null som en begränsning (användning
T : class
ellerT : BaseClass
), utan använderT?
i hela den allmänna deklarationen för att ange motsvarande nullbara referenstyp för typargumentet. slutkommentar
Den nulllbara typanteckningen, ?
, kan inte användas på ett obundet typargument.
För en typparameter T
när typargumentet är en nullbar referenstyp C?
tolkas instanser av T?
som C?
, inte C??
.
Exempel: Följande exempel visar hur nullbarheten för ett typargument påverkar nullbarheten för en deklaration av dess typparameter:
public class C { } public static class Extensions { public static void M<T>(this T? arg) where T : notnull { } } public class Test { public void M() { C? mightBeNull = new C(); C notNull = new C(); int number = 5; int? missing = null; mightBeNull.M(); // arg is C? notNull.M(); // arg is C? number.M(); // arg is int? missing.M(); // arg is int? } }
När typargumentet är en icke-nullbar typ
?
anger typanteckningen att parametern är motsvarande null-typ. När typargumentet redan är en nullbar referenstyp är parametern samma null-typ.slutexempel
Villkoret inte null anger att ett typargument som används för typparametern ska vara en icke-nullbar värdetyp eller en referenstyp som inte kan noll. Ett typargument som inte är en icke-nullbar värdetyp eller en icke-nullbar referenstyp tillåts, men en kompilator kan generera en diagnostikvarning.
Eftersom notnull
inte är ett nyckelord i primary_constraint är inte null-villkoret alltid syntaktiskt tvetydigt med class_type. Om ett namnuppslag (§12.8.4) av namnet notnull
lyckas ska det av kompatibilitetsskäl behandlas som en class_type
. Annars ska den behandlas som NOT NULL-restriktionen.
Exempel: Följande klass visar användningen av olika typer av argument mot olika begränsningar, vilket anger varningar som kan utfärdas av en kompilator.
#nullable enable public class C { } public class A<T> where T : notnull { } public class B1<T> where T : C { } public class B2<T> where T : C? { } class Test { static void M() { // nonnull constraint allows nonnullable struct type argument A<int> x1; // possible warning: nonnull constraint prohibits nullable struct type argument A<int?> x2; // nonnull constraint allows nonnullable class type argument A<C> x3; // possible warning: nonnull constraint prohibits nullable class type argument A<C?> x4; // nonnullable base class requirement allows nonnullable class type argument B1<C> x5; // possible warning: nonnullable base class requirement prohibits nullable class type argument B1<C?> x6; // nullable base class requirement allows nonnullable class type argument B2<C> x7; // nullable base class requirement allows nullable class type argument B2<C?> x8; } }
Begränsningen för värdetyp anger att ett typargument som används för typparametern ska vara en värdetyp som inte kan nulleras. Alla icke-nullbara struct-typer, uppräkningstyp och typparametrar med begränsningen för värdetyper uppfyller den här begränsningen. Observera att även om den klassificeras som en värdetyp uppfyller inte en nullbar värdetyp (§8.3.12) värdetypsbegränsningen. En typparameter med villkoret värdetyp får inte heller ha constructor_constraint, även om den kan användas som typargument för en annan typparameter med en constructor_constraint.
Obs! Typen
System.Nullable<T>
anger den icke-nullbara värdetypsbegränsningen förT
. Således är rekursivt konstruerade typer av formulärT??
ochNullable<Nullable<T>>
förbjudna. slutkommentar
Villkoret ohanterad typ anger att ett typargument som används för typparametern ska vara en icke-nullbar ohanterad typ (§8.8).
Eftersom unmanaged
inte är ett nyckelord i primary_constraint är den ohanterade begränsningen alltid syntaktiskt tvetydig med class_type. Av kompatibilitetsskäl, om ett namnuppslag (§12.8.4) av namnet unmanaged
lyckas behandlas det som en class_type
. Annars behandlas det som begränsningen utan hantering.
Pekartyper tillåts aldrig vara typargument och uppfyller inte några typbegränsningar, inte ens ohanterade, trots att de är ohanterade typer.
Om en begränsning är en klasstyp, en gränssnittstyp eller en typparameter anger den typen en minimal "bastyp" som varje typargument som används för den typparametern ska stödja. När en konstruerad typ eller generisk metod används kontrolleras typargumentet mot begränsningarna för typparametern vid kompileringstid. Det angivna typargumentet skall uppfylla de villkor som beskrivs i §8.4.5.
Ett class_type villkor ska uppfylla följande regler:
- Typen ska vara en klasstyp.
- Typen får inte vara
sealed
. - Typen får inte vara någon av följande typer:
System.Array
ellerSystem.ValueType
. - Typen får inte vara
object
. - Högst en begränsning för en viss typparameter kan vara en klasstyp.
En typ som anges som ett interface_type villkor ska uppfylla följande regler:
- Typen ska vara en gränssnittstyp.
- En typ får inte anges mer än en gång i en viss sats
where
.
I båda fallen kan villkoret omfatta någon av typparametrarna för den associerade typen eller metoddeklarationen som en del av en konstruerad typ och kan omfatta den typ som deklareras.
Alla klass- eller gränssnittstyper som anges som en typparameterbegränsning ska vara minst lika tillgängliga (§7.5.5) som den generiska typ eller metod som deklareras.
En typ som anges som en type_parameter villkor ska uppfylla följande regler:
- Typen ska vara en typparameter.
- En typ får inte anges mer än en gång i en viss sats
where
.
Dessutom får det inte finnas några cykler i beroendediagrammet av typparametrar, där beroendet är en transitiv relation som definieras av:
- Om en typparameter
T
används som en begränsning för typparameternS
beror detS
på.T
- Om en typparameter
S
är beroende av en typparameterT
ochT
är beroende av en typparameterU
beror detS
påU
.
Med den här relationen är det ett kompileringsfel för en typparameter som är beroende av sig själv (direkt eller indirekt).
Eventuella begränsningar ska vara konsekventa mellan beroende typparametrar. Om typparametern S
är beroende av typparametern T
:
-
T
ska inte ha begränsningen för värdetyp. AnnarsT
är effektivt förseglad såS
skulle tvingas vara samma typ somT
, vilket eliminerar behovet av två typparametrar. - Om
S
har begränsningen för värdetyp ska denT
inte ha något class_type villkor. - Om
S
har en class_type begränsningA
ochT
har en class_type villkorB
ska det finnas en identitetskonvertering eller implicit referenskonvertering frånA
tillB
eller en implicit referenskonvertering frånB
tillA
. - Om
S
också är beroende av typparameter ochU
U
har en class_type begränsningA
ochT
har en class_type villkorB
ska det finnas en identitetskonvertering eller implicit referenskonvertering frånA
tillB
eller en implicit referenskonvertering frånB
tillA
.
Det är giltigt att S
har begränsningen för värdetyper och att T
har begränsningen för referenstyper. I praktiken begränsar T
detta till typerna System.Object
, System.ValueType
, System.Enum
och alla gränssnittstyper.
where
Om satsen för en typparameter innehåller en konstruktorbegränsning (som har formuläret new()
) är det möjligt att använda operatorn new
för att skapa instanser av typen (§12.8.17.2). Alla typargument som används för en typparameter med en konstruktorbegränsning ska vara en värdetyp, en icke-abstrakt klass som har en offentlig parameterlös konstruktor eller en typparameter med villkoret för värdetyp eller konstruktor.
Det är ett kompileringsfel för type_parameter_constraints att ha en primary_constraint av struct
eller unmanaged
och att också ha en constructor_constraint.
Exempel: Följande är exempel på begränsningar:
interface IPrintable { void Print(); } interface IComparable<T> { int CompareTo(T value); } interface IKeyProvider<T> { T GetKey(); } class Printer<T> where T : IPrintable {...} class SortedList<T> where T : IComparable<T> {...} class Dictionary<K,V> where K : IComparable<K> where V : IPrintable, IKeyProvider<K>, new() { ... }
Följande exempel är felaktigt eftersom det orsakar en cirkulärhet i beroendediagrammet för typparametrarna:
class Circular<S,T> where S: T where T: S // Error, circularity in dependency graph { ... }
Följande exempel illustrerar ytterligare ogiltiga situationer:
class Sealed<S,T> where S : T where T : struct // Error, `T` is sealed { ... } class A {...} class B {...} class Incompat<S,T> where S : A, T where T : B // Error, incompatible class-type constraints { ... } class StructWithClass<S,T,U> where S : struct, T where T : U where U : A // Error, A incompatible with struct { ... }
slutexempel
Den dynamiska raderingen av en typ C
är en typ Cₓ
konstruerad enligt följande:
- Om
C
är en kapslad typ, så ärOuter.Inner
en kapslad typCₓ
. - Om
C
Cₓ
är en konstruerad typG<A¹, ..., Aⁿ>
med typargumentA¹, ..., Aⁿ
Cₓ
är den konstruerade typenG<A¹ₓ, ..., Aⁿₓ>
. - Om
C
är en arraytypE[]
så ärCₓ
arraytypenEₓ[]
. - Om
C
är dynamiskt är detCₓ
object
. - Annars är
Cₓ
C
.
Den effektiva basklassen för en typparameter T
definieras på följande sätt:
Låt R
vara en uppsättning typer sådana att:
- För varje begränsning av
T
som är en typparameter, innehållerR
dess effektiva basklass. - För varje begränsning av
T
som är en structtyp, innehållerR
System.ValueType
. - För varje begränsning av
T
som är en uppräkningstyp,R
innehållerSystem.Enum
. - För varje begränsning av
T
som är en delegattype, innehållerR
dess dynamiska radering. - För varje begränsning av typen matris
T
innehållerR
System.Array
. - För varje begränsning i
T
som är av typen klass, innehållerR
dess dynamiska radering.
Då
- Om
T
har begränsningen för värdetyp ärSystem.ValueType
dess effektiva basklass . - Om
R
är tom, då är den effektiva basklassenobject
. - Annars är den effektiva basklassen
T
av den mest omfattande typen (§10.5.3) av uppsättningenR
. Om uppsättningen inte har någon omfattande typ är den effektiva basklassen förT
object
. Konsekvensreglerna säkerställer att den mest omfattande typen finns.
Om typparametern är en parameter av metodtyp vars begränsningar ärvs från basmetoden beräknas den effektiva basklassen efter typersättning.
Dessa regler säkerställer att den effektiva basklassen alltid är en class_type.
Den effektiva gränssnittsuppsättningen för en typparameter T
definieras på följande sätt:
- Om
T
saknar secondary_constraints, är dess effektiva gränssnittsuppsättning tom. - Om
T
har interface_type-begränsningar men inga type_parameter-begränsningar, är dess effektiva gränssnittsuppsättning uppsättningen av dynamiska borttagningar av dess interface_type-begränsningar. - Om
T
det inte finns några interface_type begränsningar men har type_parameter begränsningar är dess effektiva gränssnittsuppsättning en union av de effektiva gränssnittsuppsättningarna för dess type_parameter begränsningar. - Om
T
har både interface_type begränsningar och type_parameter begränsningar är dess effektiva gränssnittsuppsättning en union av uppsättningen dynamiska radering av dess interface_type begränsningar och de effektiva gränssnittsuppsättningarna för dess type_parameter begränsningar.
En typparameter är känd för att vara en referenstyp om den har referenstypsbegränsningen eller om dess effektiva basklass inte object
är eller System.ValueType
. En typparameter är känd för att vara en icke-nullbar referenstyp om den är känd för att vara en referenstyp och har den icke-nullbara referenstypsbegränsningen.
Värden av en begränsad typparametertyp kan användas för att komma åt de instansmedlemmar som är underförstådda av begränsningarna.
Exempel: I följande:
interface IPrintable { void Print(); } class Printer<T> where T : IPrintable { void PrintOne(T x) => x.Print(); }
IPrintable
-metoderna kan anropas direkt påx
eftersomT
alltid måste implementeraIPrintable
.slutexempel
När en partiell allmän typdeklaration innehåller begränsningar ska begränsningarna överensstämma med alla andra delar som omfattar begränsningar. Mer specifikt ska varje del som innehåller begränsningar ha begränsningar för samma uppsättning typparametrar, och för varje typparameter ska uppsättningarna med primära, sekundära och konstruktorbegränsningar vara likvärdiga. Två uppsättningar begränsningar är likvärdiga om de innehåller samma medlemmar. Om ingen del av en partiell allmän typ anger typparameterbegränsningar anses typparametrarna vara obegränsade.
Exempel:
partial class Map<K,V> where K : IComparable<K> where V : IKeyProvider<K>, new() { ... } partial class Map<K,V> where V : IKeyProvider<K>, new() where K : IComparable<K> { ... } partial class Map<K,V> { ... }
är korrekt eftersom de delar som innehåller begränsningar (de två första) effektivt anger samma uppsättning primära, sekundära och konstruktorbegränsningar för samma uppsättning av typparametrar.
slutexempel
15.2.6 Klasskropp
Class_body för en klass definierar medlemmarna i den klassen.
class_body
: '{' class_member_declaration* '}'
;
15.2.7 Partiella typdeklarationer
Modifieraren partial
används när du definierar en klass, struct eller gränssnittstyp i flera delar. Den partial
modifieraren är ett kontextuellt nyckelord (§6.4.4) och har särskild betydelse omedelbart före nyckelorden class
, struct
och interface
. (En partiell typ kan innehålla partiella metoddeklarationer (§15.6.9).
Varje del av en partiell typdeklaration ska innehålla en partial
modifierare och deklareras i samma namnområde eller innehålla typ som de andra delarna. Modifieraren partial
anger att ytterligare delar av typdeklarationen kan finnas någon annanstans, men förekomsten av sådana ytterligare delar är inte ett krav. Det är giltigt för den enda deklarationen av en typ som innehåller partial
modifieraren. Det är giltigt för endast en deklaration av en partiell typ att inkludera basklassen eller implementerade gränssnitt. Alla deklarationer av en basklass eller implementerade gränssnitt ska dock matcha, inklusive ogiltigheten för alla angivna typargument.
Alla delar av en partiell typ ska sammanställas så att delarna kan sammanfogas vid kompileringstillfället. Partiella typer tillåter inte att redan kompilerade typer utökas.
Kapslade typer kan deklareras i flera delar med hjälp av partial
modifieraren. Vanligtvis deklareras även den innehållande typen med partial
och varje del av den kapslade typen deklareras i en annan del av den innehållande typen.
Exempel: Följande partiella klass implementeras i två delar, som finns i olika kompileringsenheter. Den första delen är maskinellt genererad av ett databasmappningsverktyg medan den andra delen är skapad manuellt.
public partial class Customer { private int id; private string name; private string address; private List<Order> orders; public Customer() { ... } } // File: Customer2.cs public partial class Customer { public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted); public bool HasOutstandingOrders() => orders.Count > 0; }
När de två delarna ovan kompileras tillsammans fungerar den resulterande koden som om klassen hade skrivits som en enda enhet, enligt följande:
public class Customer { private int id; private string name; private string address; private List<Order> orders; public Customer() { ... } public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted); public bool HasOutstandingOrders() => orders.Count > 0; }
slutexempel
Hanteringen av attribut som anges på typ- eller typparametrarna för olika delar av en partiell typdeklaration beskrivs i §22.3.
15.3 Klassmedlemmar
15.3.1 Allmänt
Medlemmarna i en klass består av de medlemmar som introduceras av dess class_member_declarationoch de medlemmar som ärvts från den direkta basklassen.
class_member_declaration
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| operator_declaration
| constructor_declaration
| finalizer_declaration
| static_constructor_declaration
| type_declaration
;
Medlemmarna i en klass är indelade i följande kategorier:
- Konstanter, som representerar konstanta värden som är associerade med klassen (§15.4).
- Fält, som är variablerna i klassen (§15.5).
- Metoder som implementerar de beräkningar och åtgärder som kan utföras av klassen (§15.6).
- Egenskaper, som definierar namngivna egenskaper och de åtgärder som är associerade med att läsa och skriva dessa egenskaper (§15.7).
- Händelser som definierar meddelanden som kan genereras av klassen (§15.8).
- Indexerare, som tillåter att instanser av klassen indexeras på samma sätt (syntaktiskt) som matriser (§15.9).
- Operatorer som definierar de uttrycksoperatorer som kan tillämpas på instanser av klassen (§15.10).
- Instanskonstruktorer som implementerar de åtgärder som krävs för att initiera instanser av klassen (§15.11)
- Slutförare, som implementerar de åtgärder som ska utföras innan instanser av klassen tas bort permanent (§15.13).
- Statiska konstruktorer som implementerar de åtgärder som krävs för att initiera själva klassen (§15.12).
- Typer, som representerar de typer som är lokala för klassen (§14.7).
En class_declaration skapar ett nytt deklarationsutrymme (§7.3) och typ-parametrarna samt de class_member_declaration som omedelbart ingår i class_declaration introducerar nya medlemmar i detta deklarationsutrymme. Följande regler gäller för class_member_declarations:
Instanskonstruktorer, finalizers och statiska konstruktorer ska ha samma namn som den omedelbart omslutande klassen. Alla andra medlemmar ska ha namn som skiljer sig från namnet på den omedelbart omslutande klassen.
Namnet på en typparameter i type_parameter_list för en klassdeklaration ska skilja sig från namnen på alla andra typparametrar i samma type_parameter_list och ska skilja sig från namnet på klassen och namnen på alla medlemmar i klassen.
Namnet på en typ ska skilja sig från namnen på alla icke-typmedlemmar som deklarerats i samma klass. Om två eller flera typdeklarationer har samma fullständigt kvalificerade namn ska deklarationerna ha
partial
modifieraren (§15.2.7) och dessa deklarationer kombineras för att definiera en enda typ.
Obs! Eftersom det fullständigt kvalificerade namnet på en typdeklaration kodar antalet typparametrar kan två olika typer dela samma namn så länge de har olika antal typparametrar. slutkommentar
Namnet på en konstant, ett fält, en egenskap eller en händelse ska skilja sig från namnen på alla andra medlemmar som deklarerats i samma klass.
Namnet på en metod ska skilja sig från namnen på alla andra icke-metoder som deklareras i samma klass. Dessutom ska signaturen (§7.6) för en metod skilja sig från signaturerna för alla andra metoder som deklarerats i samma klass, och två metoder som deklarerats i samma klass får inte ha signaturer som skiljer sig enbart
in
från ,out
ochref
.Signaturen för en instanskonstruktor ska skilja sig från signaturerna för alla andra instanskonstruktorer som deklarerats i samma klass, och två konstruktorer som deklarerats i samma klass får inte ha signaturer som skiljer sig enbart efter
ref
ochout
.En indexerares signatur ska skilja sig från signaturerna för alla andra indexerare som deklarerats i samma klass.
En operatörs underskrift ska skilja sig från signaturerna för alla andra aktörer som deklarerats i samma klass.
De ärvda medlemmarna i en klass (§15.3.4) ingår inte i en klasss deklarationsutrymme.
Obs! Därför kan en härledd klass deklarera en medlem med samma namn eller signatur som en ärvd medlem (som i själva verket döljer den ärvda medlemmen). slutkommentar
Medlemsuppsättningen för en typ som deklareras i flera delar (§15.2.7) utgör unionen av medlemmarna som deklareras i varje del. Organen i alla delar av typdeklarationen delar samma deklarationsutrymme (§7.3), och varje medlems omfattning (§7.7) sträcker sig till alla delars organ. Tillgänglighetsdomänen för alla medlemmar innehåller alltid alla delar av den omslutande typen, en privat medlem som deklareras i en del är fritt tillgänglig från en annan del. Det är ett kompileringsfel att deklarera samma medlem i mer än en del av typen, såvida inte medlemmen har partial
-modifieraren.
Exempel:
partial class A { int x; // Error, cannot declare x more than once partial void M(); // Ok, defining partial method declaration partial class Inner // Ok, Inner is a partial type { int y; } } partial class A { int x; // Error, cannot declare x more than once partial void M() { } // Ok, implementing partial method declaration partial class Inner // Ok, Inner is a partial type { int z; } }
slutexempel
Fältinitieringsordningen kan vara betydande inom C#-koden och vissa garantier ges enligt definitionen i §15.5.6.1. I annat fall är ordningen på medlemmar inom en typ sällan betydande, men kan vara betydande när de samverkar med andra språk och miljöer. I dessa fall är ordningen på medlemmar inom en typ som deklarerats i flera delar odefinierad.
15.3.2 Instanstypen
Varje klassdeklaration har en associerad instanstyp. För en allmän klassdeklaration bildas instanstypen genom att skapa en konstruerad typ (§8.4) från typdeklarationen, där var och en av de angivna typargumenten är motsvarande typparameter. Eftersom instanstypen använder typparametrarna kan den bara användas där typparametrarna finns i omfånget. det vill: i klassdeklarationen. Instanstypen är typen av this
för kod som skrivits i klassdeklarationen. För icke-generiska klasser är instanstypen helt enkelt den deklarerade klassen.
Exempel: Följande visar flera klassdeklarationer tillsammans med deras instanstyper:
class A<T> // instance type: A<T> { class B {} // instance type: A<T>.B class C<U> {} // instance type: A<T>.C<U> } class D {} // instance type: D
slutexempel
15.3.3 Medlemmar av konstruerade typer
De icke-ärvda medlemmarna av en konstruktionstyp erhålls genom att, för varje type_parameter i medlemsdeklarationen, ersätta motsvarande type_argument av den konstruerade typen. Ersättningsprocessen baseras på den semantiska innebörden av typdeklarationer och är inte bara textersättning.
Exempel: Med den generiska klassdeklarationen
class Gen<T,U> { public T[,] a; public void G(int i, T t, Gen<U,T> gt) {...} public U Prop { get {...} set {...} } public int H(double d) {...} }
den konstruerade typen
Gen<int[],IComparable<string>>
har följande medlemmar:public int[,][] a; public void G(int i, int[] t, Gen<IComparable<string>,int[]> gt) {...} public IComparable<string> Prop { get {...} set {...} } public int H(double d) {...}
Typen av medlem
a
i den generiska klassdeklarationenGen
är "tvådimensionell matris avT
", så typen av medlemmena
i den konstruerade typen ovan är "tvådimensionell matris av endimensionell matris avint
", ellerint[,][]
.slutexempel
Inom instansfunktionens medlemmar är typen av this
instanstypen (§15.3.2) av den innefattande deklarationen.
Alla medlemmar i en generisk klass kan använda typparametrar från valfri omslutande klass, antingen direkt eller som en del av en konstruerad typ. När en viss sluten konstruktionstyp (§8.4.3) används vid körning ersätts varje användning av en typparameter med typargumentet som levereras till den konstruerade typen.
Exempel:
class C<V> { public V f1; public C<V> f2; public C(V x) { this.f1 = x; this.f2 = this; } } class Application { static void Main() { C<int> x1 = new C<int>(1); Console.WriteLine(x1.f1); // Prints 1 C<double> x2 = new C<double>(3.1415); Console.WriteLine(x2.f1); // Prints 3.1415 } }
slutexempel
15.3.4 Arv
En klass ärver medlemmarna i dess direkta basklass. Arv innebär att en klass implicit innehåller alla medlemmar i dess direkta basklass, förutom instanskonstruktorer, finalizers och statiska konstruktorer för basklassen. Några viktiga aspekter av arv är:
Arv är transitivt. Om
C
härleds frånB
, ochB
härleds frånA
, ärver deC
medlemmar som deklareras iB
samt de medlemmar som deklareras iA
.En härledd klass utökar sin direkta basklass. En härledd klass kan lägga till nya medlemmar till dem som den ärver, men den kan inte ta bort definitionen av en ärvd medlem.
Instanskonstruktorer, finalizers och statiska konstruktorer ärvs inte, men alla andra medlemmar är det, oavsett deras deklarerade tillgänglighet (§7.5). Men beroende på deras deklarerade tillgänglighet kanske ärvda medlemmar inte är tillgängliga i en härledd klass.
En härledd klass kan dölja (§7.7.2.3) ärvda medlemmar genom att deklarera nya medlemmar med samma namn eller signatur. Att dölja en ärvd medlem tar dock inte bort den medlemmen– det gör bara medlemmen otillgänglig direkt via den härledda klassen.
En instans av en klass innehåller en uppsättning alla instansfält som deklarerats i klassen och dess basklasser, och en implicit konvertering (§10.2.8) finns från en härledd klasstyp till någon av dess basklasstyper. Därför kan en referens till en instans av någon härledd klass behandlas som en referens till en instans av någon av dess basklasser.
En klass kan deklarera virtuella metoder, egenskaper, indexerare och händelser och härledda klasser kan åsidosätta implementeringen av dessa funktionsmedlemmar. Detta gör det möjligt för klasser att uppvisa polymorft beteende där åtgärderna som utförs av en funktionsmedlemsanrop varierar beroende på körningstypen för den instans genom vilken funktionsmedlemmen anropas.
De ärvda medlemmarna av en konstruerad klasstyp är medlemmar av den omedelbara basklasstypen (§15.2.4.2), som hittas genom att ersätta typargumenten för den konstruerade typen för varje förekomst av motsvarande typparametrar i base_class_specification. Dessa medlemmar omvandlas i sin tur genom att byta ut i medlemsdeklarationen för varje type_parameter mot det motsvarande type_argument från base_class_specification.
Exempel:
class B<U> { public U F(long index) {...} } class D<T> : B<T[]> { public T G(string s) {...} }
I koden ovan har den konstruerade typen
D<int>
en icke-ärvd offentlig medlemint
G(string s)
, erhållen genom att ersätta typargumentetint
med typparameternT
.D<int>
har också en ärvd medlem från klassdeklarationenB
. Den här ärvda medlemmen bestäms genom att först fastställa basklasstypenB<int[]>
avD<int>
genom att ersättaint
medT
i basklassspecifikationenB<T[]>
. Sedan, som ett typargument tillB
,int[]
ersätts medU
ipublic U F(long index)
, vilket ger den ärvda medlemmenpublic int[] F(long index)
.slutexempel
15.3.5 Den nya modifieraren
En class_member_declaration kan deklarera en medlem med samma namn eller signatur som en ärvd medlem. När detta inträffar sägs den härledda klassmedlemmen dölja basklassmedlemmen. Se §7.7.2.3 för en exakt specifikation av när en medlem döljer en ärvd medlem.
En ärvd medlem M
anses vara tillgänglig om M
den är tillgänglig och det inte finns någon annan ärvd tillgänglig medlem N som redan döljer M
. Att implicit dölja en ärvd medlem anses inte vara ett fel, men en kompilator ska utfärda en varning om inte deklarationen av den härledda klassmedlemmen innehåller en new
modifierare som uttryckligen anger att den härledda medlemmen är avsedd att dölja basmedlemmen. Om en eller flera delar av en partiell deklaration (§15.2.7) av kapslad typ innehåller new
modifieraren, utfärdas ingen varning om den kapslade typen döljer en tillgänglig ärvd medlem.
Om en new
modifierare ingår i en deklaration som inte döljer en tillgänglig ärvd medlem utfärdas en varning om detta.
15.3.6 Åtkomstmodifierare
En class_member_declaration kan ha någon av de tillåtna typerna av deklarerad tillgänglighet (§7.5.2): public
, protected internal
, protected
, private protected
, internal
eller private
. Förutom kombinationerna protected internal
och private protected
är det ett kompileringsfel att ange mer än en åtkomstmodifierare. När en class_member_declaration inte innehåller några åtkomstmodifierare antas private
.
15.3.7 Komponenttyper
Typer som används i deklarationen av en medlem kallas för medlemskomponenttyper. Möjliga komponenttyper är typen av konstant, fält, egenskap, händelse eller indexerare, returtypen för en metod eller operator och parametertyperna för en metod, indexerare, operator eller instanskonstruktor. En medlems konstituerande typer ska vara minst lika tillgängliga som medlemmen själv (§7.5.5).
15.3.8 Statiska medlemmar och instansmedlemmar
Medlemmar i en klass är antingen statiska medlemmar eller instansmedlemmar.
Obs! Generellt sett är det bra att tänka på statiska medlemmar som tillhör klasser och instansmedlemmar som tillhör objekt (instanser av klasser). slutkommentar
När en fält-, metod-, egenskaps-, händelse-, operator- eller konstruktordeklaration innehåller en static
modifierare deklareras en statisk medlem. Dessutom deklarerar en konstant- eller typdeklaration implicit en statisk medlem. Statiska medlemmar har följande egenskaper:
- När en statisk medlem
M
refereras i en member_access (§12.8.7) i formuläretE.M
skaE
en typ som har en medlemM
anges . Det är ett kompileringsfel att beteckna en instans medE
. - Ett statiskt fält i en icke-generisk klass identifierar exakt en lagringsplats. Oavsett hur många instanser av en icke-generisk klass som skapas finns det bara en kopia av ett statiskt fält. Varje distinkt sluten konstruktionstyp (§8.4.3) har en egen uppsättning statiska fält, oavsett antalet instanser av den stängda konstruktionstypen.
- En statisk funktionsmedlem (metod, egenskap, händelse, operator eller konstruktor) fungerar inte på en specifik instans, och det är ett kompileringsfel att referera till detta i en sådan funktionsmedlem.
När en fält-, metod-, egenskaps-, händelse-, indexerare-, konstruktor- eller finalizerdeklaration inte innehåller någon statisk modifierare deklareras en instansmedlem. (En instansmedlem kallas ibland för en icke-statisk medlem.) Instansmedlemmar har följande egenskaper:
- När en instansmedlem
M
refereras i en member_access (§12.8.7) i formuläretE.M
,E
ska en instans av en typ som har en medlemM
anges. Det är ett bindningstidsfel för E att beteckna en typ. - Varje instans av en klass innehåller en separat uppsättning av alla instansfält i klassen.
- En instansfunktionsmedlem (metod, egenskap, indexerare, instanskonstruktor eller finalizer) fungerar på en viss instans av klassen, och den här instansen kan nås som
this
(§12.8.14).
Exempel: I följande exempel visas reglerna för åtkomst till statiska medlemmar och instansmedlemmar:
class Test { int x; static int y; void F() { x = 1; // Ok, same as this.x = 1 y = 1; // Ok, same as Test.y = 1 } static void G() { x = 1; // Error, cannot access this.x y = 1; // Ok, same as Test.y = 1 } static void Main() { Test t = new Test(); t.x = 1; // Ok t.y = 1; // Error, cannot access static member through instance Test.x = 1; // Error, cannot access instance member through type Test.y = 1; // Ok } }
Metoden
F
visar att i en instansfunktionsmedlem kan en simple_name (§12.8.4) användas för att komma åt både instansmedlemmar och statiska medlemmar. MetodenG
visar att i en statisk funktionsmedlem är det ett kompileringsfel att komma åt en instansmedlem via en simple_name. MetodenMain
visar att i en member_access (§12.8.7) ska instansmedlemmar nås via instanser och statiska medlemmar ska nås via typer.slutexempel
15.3.9 Kapslade typer
15.3.9.1 Allmänt
En typ som deklareras i en klass eller struktur kallas en kapslad typ. En typ som deklareras i en kompileringsenhet eller ett namnområde kallas för en icke-kapslad typ.
Exempel: I följande exempel:
class A { class B { static void F() { Console.WriteLine("A.B.F"); } } }
klass
B
är en kapslad typ eftersom den deklareras i klassenA
, och klassenA
är en icke-kapslad typ eftersom den deklareras i en kompileringsenhet.slutexempel
15.3.9.2 Fullständigt kvalificerat namn
Det fullständigt kvalificerade namnet (§7.8.3) för en kapslad typdeklaration är S.N
där S
är det fullständigt kvalificerade namnet på den typdeklaration där typ N
deklareras och N
är det okvalificerade namnet (§7.8.2) av den kapslade typdeklarationen (inklusive alla generic_dimension_specifier (§12.8.18)).
15.3.9.3 Deklarerad tillgänglighet
Ej kapslade typer kan ha public
eller internal
deklarerad tillgänglighet och har internal
deklarerad tillgänglighet som standard. Kapslade typer kan också ha dessa former av deklarerad tillgänglighet, plus en eller flera ytterligare former av deklarerad tillgänglighet, beroende på om den innehållande typen är en klass eller struct:
- En kapslad typ som deklareras i en klass kan ha någon av de tillåtna nivåerna av deklarerad tillgänglighet och har, i likhet med andra klassmedlemmar, som standard samma deklarerade tillgänglighet som
private
. - En nästlad typ som deklareras i en strukt kan ha någon av tre former av deklarerad åtkomst (
public
,internal
, ellerprivate
) och, precis som andra struktmedlemmar, har som standardprivate
deklarerad åtkomst.
Exempel: Exemplet
public class List { // Private data structure private class Node { public object Data; public Node? Next; public Node(object data, Node? next) { this.Data = data; this.Next = next; } } private Node? first = null; private Node? last = null; // Public interface public void AddToFront(object o) {...} public void AddToBack(object o) {...} public object RemoveFromFront() {...} public object RemoveFromBack() {...} public int Count { get {...} } }
deklarerar en privat inkapslad klass
Node
.slutexempel
15.3.9.4 Döljer
En inbäddad typ kan dölja (§7.7.2.2) en basmedlem. Modifieraren new
(§15.3.5) är tillåten på kapslade typdeklarationer så att döljande kan uttryckas explicit.
Exempel: Exemplet
class Base { public static void M() { Console.WriteLine("Base.M"); } } class Derived: Base { public new class M { public static void F() { Console.WriteLine("Derived.M.F"); } } } class Test { static void Main() { Derived.M.F(); } }
visar en kapslad klass
M
som döljer metodenM
som definierats iBase
.slutexempel
15.3.9.5 denna åtkomst
En kapslad typ och dess omgivande typ har ingen särskild relation gällande this_access (§12.8.14). Mer specifikt kan this
inom en kapslad typ inte användas för att referera till instansmedlemmar av den innehållande typen. I de fall där en kapslad typ behöver åtkomst till instansmedlemmarna av dess innehållande typ kan åtkomst tillhandahållas genom att ange this
för instansen av den innehållande typen som ett konstruktorargument för den kapslade typen.
Exempel: Följande exempel
class C { int i = 123; public void F() { Nested n = new Nested(this); n.G(); } public class Nested { C this_c; public Nested(C c) { this_c = c; } public void G() { Console.WriteLine(this_c.i); } } } class Test { static void Main() { C c = new C(); c.F(); } }
visar den här tekniken. En instans av
C
skapar en instans avNested
och skickar sin egen tillNested
konstruktorn för att ge efterföljande åtkomst tillC
instansmedlemmar.slutexempel
15.3.9.6 Åtkomst till privata och skyddade medlemmar av den innehållande typen
En kapslad typ har åtkomst till alla medlemmar som är tillgängliga för dess innehållande typ, inklusive medlemmar av den innehållande typen som har private
och protected
deklarerade åtkomstnivåer.
Exempel: Exemplet
class C { private static void F() => Console.WriteLine("C.F"); public class Nested { public static void G() => F(); } } class Test { static void Main() => C.Nested.G(); }
visar en klass
C
som innehåller en kapslad klassNested
. INested
anropar metodenG
den statiska metodenF
som definierats iC
ochF
har privat deklarerad tillgänglighet.slutexempel
En kapslad typ kan också komma åt skyddade medlemmar som definierats i en bastyp av dess innehållande typ.
Exempel: I följande kod
class Base { protected void F() => Console.WriteLine("Base.F"); } class Derived: Base { public class Nested { public void G() { Derived d = new Derived(); d.F(); // ok } } } class Test { static void Main() { Derived.Nested n = new Derived.Nested(); n.G(); } }
den kapslade klassen
Derived.Nested
kommer åt den skyddade metodenF
som definierats iDerived
basklassen ,Base
genom att anropa via en instans avDerived
.slutexempel
15.3.9.7 Kapslade typer i allmänna klasser
En allmän klassdeklaration kan innehålla kapslade typdeklarationer. Typparametrarna för den omslutande klassen kan användas inom de kapslade typerna. En kapslad typdeklaration kan innehålla ytterligare typparametrar som endast gäller för den kapslade typen.
Varje typdeklaration som finns i en allmän klassdeklaration är implicit en allmän typdeklaration. När du skriver en referens till en typ kapslad inom en generisk typ, ska den innehållande konstruerade typen, inklusive dess typargument, namnges. Från den yttre klassen kan dock den kapslade typen användas utan kvalificering. instanstypen för den yttre klassen kan implicit användas när den kapslade typen konstrueras.
Exempel: Följande visar tre olika sätt att referera till en konstruerad typ som skapats från
Inner
; de två första är likvärdiga:class Outer<T> { class Inner<U> { public static void F(T t, U u) {...} } static void F(T t) { Outer<T>.Inner<string>.F(t, "abc"); // These two statements have Inner<string>.F(t, "abc"); // the same effect Outer<int>.Inner<string>.F(3, "abc"); // This type is different Outer.Inner<string>.F(t, "abc"); // Error, Outer needs type arg } }
slutexempel
Även om det är ett felaktigt programmeringsformat kan en typparameter i en kapslad typ dölja en medlem eller typparameter som deklarerats i den yttre typen.
Exempel:
class Outer<T> { class Inner<T> // Valid, hides Outer's T { public T t; // Refers to Inner's T } }
slutexempel
15.3.10 Reserverade medlemsnamn
15.3.10.1 Allmänt
För att underlätta den underliggande C#-körningen ska implementeringen för varje källmedlemsdeklaration som är en egenskap, händelse eller indexerare reservera två metodsignaturer baserade på typen av medlemsdeklaration, dess namn och dess typ (§15.3.10.2, §15.3.10.3, §15.3.10.4). Det är ett kompileringsfel för ett program att deklarera en medlem vars signatur matchar en signatur som reserverats av en medlem som deklarerats i samma omfång, även om den underliggande körningsimplementeringen inte använder dessa reservationer.
De reserverade namnen introducerar inte deklarationer, vilket innebär att de inte deltar i medlemssökning. En deklarations associerade reserverade metodsignaturer deltar dock i arv (§15.3.4) och kan döljas med new
modifieraren (§15.3.5).
Obs! Reservationen av dessa namn har tre syften:
- För att tillåta den underliggande implementeringen att använda en vanlig identifierare som ett metodnamn för att hämta eller ange åtkomst till C#-språkfunktionen.
- Om du vill tillåta att andra språk samverkar med en vanlig identifierare som ett metodnamn för att hämta eller ange åtkomst till C#-språkfunktionen.
- För att säkerställa att källan som accepteras av en kompilator som uppfyller kraven godkänns av en annan, genom att göra detaljerna för reserverade medlemsnamn konsekventa för alla C#-implementeringar.
slutkommentar
Deklarationen av en finalator (§15.13) gör också att en signatur reserveras (§15.3.10.5).
Vissa namn är reserverade för användning som operatormetodnamn (§15.3.10.6).
15.3.10.2 Medlemsnamn reserverade för egenskaper
För en egenskap P
(§15.7) av typen T
är följande signaturer reserverade:
T get_P();
void set_P(T value);
Båda signaturerna är reserverade, även om egenskapen är skrivskyddad eller endast skrivbar.
Exempel: I följande kod
class A { public int P { get => 123; } } class B : A { public new int get_P() => 456; public new void set_P(int value) { } } class Test { static void Main() { B b = new B(); A a = b; Console.WriteLine(a.P); Console.WriteLine(b.P); Console.WriteLine(b.get_P()); } }
En klass
A
definierar en skrivskyddad egenskapP
, vilket reserverar signaturer förget_P
ochset_P
metoder.A
-klassenB
härleds frånA
och döljer båda dessa reserverade signaturer. Exemplet genererar utdata:123 123 456
slutexempel
15.3.10.3 Medlemsnamn reserverade för händelser
För en händelse E
(§15.8) av ombudstyp T
är följande signaturer reserverade:
void add_E(T handler);
void remove_E(T handler);
15.3.10.4 Medlemsnamn reserverade för indexerare
För en indexerare (§15.9) av typen T
med parameter-list L
är följande signaturer reserverade:
T get_Item(L);
void set_Item(L, T value);
Båda signaturerna är reserverade, även om indexeraren är skrivskyddad eller skrivbar.
Dessutom är medlemsnamnet Item
reserverat.
15.3.10.5 Medlemsnamn reserverade för finalizers
För en klass som innehåller en finalator (§15.13) är följande signatur reserverad:
void Finalize();
15.3.10.6 Metodnamn reserverade för operatorer
Följande metodnamn är reserverade. Även om många har motsvarande operatorer i den här specifikationen är vissa reserverade för användning av framtida versioner, medan vissa är reserverade för interop med andra språk.
Metodnamn | C#-operator |
---|---|
op_Addition |
+ (binärt) |
op_AdditionAssignment |
(reserverad) |
op_AddressOf |
(reserverad) |
op_Assign |
(reserverad) |
op_BitwiseAnd |
& (binärt) |
op_BitwiseAndAssignment |
(reserverad) |
op_BitwiseOr |
\| |
op_BitwiseOrAssignment |
(reserverad) |
op_CheckedAddition |
(reserverad för framtida användning) |
op_CheckedDecrement |
(reserverad för framtida användning) |
op_CheckedDivision |
(reserverad för framtida användning) |
op_CheckedExplicit |
(reserverad för framtida användning) |
op_CheckedIncrement |
(reserverad för framtida användning) |
op_CheckedMultiply |
(reserverad för framtida användning) |
op_CheckedSubtraction |
(reserverad för framtida användning) |
op_CheckedUnaryNegation |
(reserverad för framtida användning) |
op_Comma |
(reserverad) |
op_Decrement |
-- (prefix och postfix) |
op_Division |
/ |
op_DivisionAssignment |
(reserverad) |
op_Equality |
== |
op_ExclusiveOr |
^ |
op_ExclusiveOrAssignment |
(reserverad) |
op_Explicit |
explicit (inskränkande) tvång |
op_False |
false |
op_GreaterThan |
> |
op_GreaterThanOrEqual |
>= |
op_Implicit |
implicit (vidgade) tvång |
op_Increment |
++ (prefix och postfix) |
op_Inequality |
!= |
op_LeftShift |
<< |
op_LeftShiftAssignment |
(reserverad) |
op_LessThan |
< |
op_LessThanOrEqual |
<= |
op_LogicalAnd |
(reserverad) |
op_LogicalNot |
! |
op_LogicalOr |
(reserverad) |
op_MemberSelection |
(reserverad) |
op_Modulus |
% |
op_ModulusAssignment |
(reserverad) |
op_MultiplicationAssignment |
(reserverad) |
op_Multiply |
* (binärt) |
op_OnesComplement |
~ |
op_PointerDereference |
(reserverad) |
op_PointerToMemberSelection |
(reserverad) |
op_RightShift |
>> |
op_RightShiftAssignment |
(reserverad) |
op_SignedRightShift |
(reserverad) |
op_Subtraction |
- (binärt) |
op_SubtractionAssignment |
(reserverad) |
op_True |
true |
op_UnaryNegation |
- (unär) |
op_UnaryPlus |
+ (unär) |
op_UnsignedRightShift |
(reserverad för framtida användning) |
op_UnsignedRightShiftAssignment |
(reserverad) |
15.4 Konstanter
En konstant är en klassmedlem som representerar ett konstant värde: ett värde som kan beräknas vid kompileringstid. En constant_declaration introducerar en eller flera konstanter av en viss typ.
constant_declaration
: attributes? constant_modifier* 'const' type constant_declarators ';'
;
constant_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
;
En constant_declaration kan innehålla en uppsättning attribut (§22), en new
modifierare (§15.3.5) och någon av de tillåtna typerna av deklarerad tillgänglighet (§15.3.6). Attributen och modifierarna gäller för alla medlemmar som deklareras av constant_declaration. Även om konstanter anses vara statiska medlemmar, varken kräver eller tillåter en constant_declaration en static
modifierare. Det är ett fel att samma modifierare visas flera gånger i en konstant deklaration.
Typen av constant_declaration anger vilken typ av medlemmar som infördes i deklarationen. Typen följs av en lista över constant_declarator s (§13.6.3), som var och en introduceraren ny medlem. En constant_declarator består av en identifierare som namnger medlemmen, följt av en "=
" token, följt av en constant_expression (§12.23) som ger medlemmens värde.
Den typ som anges i en konstant deklaration ska vara sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, char
float
, double
, decimal
, bool
, en string
enum_type eller en reference_type. Varje constant_expression ska ge ett värde av måltypen eller av en typ som kan konverteras till måltypen genom en implicit konvertering (§10.2).
Typen av konstant ska vara minst lika tillgänglig som konstanten själv (§7.5.5).
Värdet för en konstant erhålls i ett uttryck med hjälp av en simple_name (§12.8.4) eller en member_access (§12.8.7).
En konstant kan själv delta i en constant_expression. En konstant kan därför användas i alla konstruktioner som kräver en constant_expression.
Obs! Exempel på sådana konstruktioner är
case
etiketter,goto case
instruktioner,enum
medlemsdeklarationer, attribut och andra konstanta deklarationer. slutkommentar
Obs! Enligt beskrivningen i §12.23 är en constant_expression ett uttryck som kan utvärderas fullständigt vid kompileringstid. Eftersom det enda sättet att skapa ett icke-null-värde för en annan reference_type än
string
är att tillämpa operatornnew
, och eftersom operatorn inte är tillåtennew
i en constant_expression, är det enda möjliga värdet för konstanter avstring
andra ännull
. slutkommentar
När ett symboliskt namn för ett konstant värde önskas, men när typen av det värdet inte tillåts i en konstant deklaration, eller när värdet inte kan beräknas vid kompilering av en constant_expression, kan ett skrivskyddat fält (§15.5.3) användas i stället.
Obs! Versionssemantiken för
const
ochreadonly
skiljer sig åt (§15.5.3.3). slutkommentar
En konstant deklaration som deklarerar flera konstanter motsvarar flera deklarationer av enskilda konstanter med samma attribut, modifierare och typ.
Exempel:
class A { public const double X = 1.0, Y = 2.0, Z = 3.0; }
motsvarar
class A { public const double X = 1.0; public const double Y = 2.0; public const double Z = 3.0; }
slutexempel
Konstanter tillåts vara beroende av andra konstanter inom samma program så länge beroendena inte är av cirkulär karaktär.
Exempel: I följande kod
class A { public const int X = B.Z + 1; public const int Y = 10; } class B { public const int Z = A.Y + 1; }
en kompilator måste först utvärdera
A.Y
, sedan utvärderaB.Z
och slutligen utvärderaA.X
, producera värdena10
,11
och12
.slutexempel
Konstanta deklarationer kan bero på konstanter från andra program, men sådana beroenden är bara möjliga i en riktning.
Exempel: Med hänvisning till exemplet ovan, om
A
ochB
deklarerades i separata program, skulle det vara möjligt förA.X
att vara beroendeB.Z
av , menB.Z
kan då inte samtidigt vara beroendeA.Y
av . slutexempel
15.5 Fält
15.5.1 Allmänt
Ett fält är en medlem som representerar en variabel som är associerad med ett objekt eller en klass. En field_declaration introducerar ett eller flera fält av en viss typ.
field_declaration
: attributes? field_modifier* type variable_declarators ';'
;
field_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'readonly'
| 'volatile'
| unsafe_modifier // unsafe code support
;
variable_declarators
: variable_declarator (',' variable_declarator)*
;
variable_declarator
: identifier ('=' variable_initializer)?
;
unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).
En field_declaration kan innehålla en uppsättning attribut (§22), en new
modifierare (§15.3.5), en giltig kombination av de fyra åtkomstmodifierarna (§15.3.6) och en static
modifierare (§15.5.2). Dessutom kan en field_declaration innehålla en readonly
modifierare (§15.5.3) eller en volatile
modifierare (§15.5.4), men inte båda. Attributen och modifierarna gäller för alla medlemmar som deklareras av field_declaration. Det är ett fel att samma modifierare visas flera gånger i en field_declaration.
Typen av field_declaration anger vilken typ av medlemmar som infördes i deklarationen. Typen följs av en lista över variable_declarators, som var och en introducerar en ny medlem. En variable_declarator består av en identifierare som namnger medlemmen, eventuellt följt av en "=
" token och en variable_initializer (§15.5.6) som ger medlemmens ursprungliga värde.
Typen av fält ska vara minst lika tillgänglig som själva fältet (§7.5.5).
Värdet för ett fält erhålls i ett uttryck med hjälp av en simple_name (§12.8.4), en member_access (§12.8.7) eller en base_access (§12.8.15). Värdet på ett fält som inte är skrivskyddat ändras genom en tilldelning (§12.21). Värdet för ett icke-skrivskyddat fält kan både hämtas och ändras med postfix inkrement- och dekrementoperatorer (§12.8.16) och prefix inkrement- och dekrementoperatorer (§12.9.6).
En fältdeklaration som deklarerar flera fält motsvarar flera deklarationer av enkla fält med samma attribut, modifierare och typ.
Exempel:
class A { public static int X = 1, Y, Z = 100; }
motsvarar
class A { public static int X = 1; public static int Y; public static int Z = 100; }
slutexempel
15.5.2 Statiska fält och instansfält
När en fältdeklaration innehåller en static
modifierare är fälten som introduceras av deklarationen statiska fält. När det inte finns någon static
modifierare är fälten som introduceras av deklarationen instansfält. Statiska fält och instansfält är två av flera typer av variabler (§9) som stöds av C#, och ibland kallas de statiska variabler respektive instansvariabler.
Som förklaras i §15.3.8 innehåller varje instans av en klass en fullständig uppsättning instansfält för klassen, medan det bara finns en uppsättning statiska fält för varje icke-generisk klass eller stängd konstruktionstyp, oavsett antalet instanser av klassen eller den stängda konstruerade typen.
15.5.3 Skrivskyddade fält
15.5.3.1 Allmänt
När en field_declaration innehåller en readonly
modifierare är fälten som introduceras av deklarationen readonly-fält. Direkttilldelningar till readonly-fält kan endast ske som en del av den deklarationen eller i en instanskonstruktor eller statisk konstruktor i samma klass. (A readonly-fält kan tilldelas flera gånger i dessa sammanhang.) Specifikt är direkta tilldelningar till ett skrivskyddat fält endast tillåtna i följande sammanhang:
- I variable_declarator som introducerar fältet (genom att inkludera en variable_initializer i deklarationen).
- För ett instansfält, i instanskonstruktorerna för klassen som innehåller fältdeklarationen; för ett statiskt fält, i den statiska konstruktorn för klassen som innehåller fältdeklarationen. Detta är också de enda sammanhangen där det är giltigt att överföra ett readonly fält som en ut- eller referensparameter.
Att försöka tilldela ett skrivskyddat fält eller använda det som en utdata- eller referensparameter i någon annan kontext är ett kompileringsfel.
15.5.3.2 Använda statiska readonly-fält för konstanter
Ett statiskt skrivskyddat fält är användbart när ett symboliskt namn för ett konstant värde önskas, men när typen av värdet inte tillåts i en const-deklaration eller när värdet inte kan beräknas vid kompileringstid.
Exempel: I följande kod
public class Color { public static readonly Color Black = new Color(0, 0, 0); public static readonly Color White = new Color(255, 255, 255); public static readonly Color Red = new Color(255, 0, 0); public static readonly Color Green = new Color(0, 255, 0); public static readonly Color Blue = new Color(0, 0, 255); private byte red, green, blue; public Color(byte r, byte g, byte b) { red = r; green = g; blue = b; } }
Black
medlemmarna ,White
,Red
,Green
ochBlue
kan inte deklareras som const-medlemmar eftersom deras värden inte kan beräknas vid kompileringstid. Men att deklarera demstatic readonly
i stället har ungefär samma effekt.slutexempel
15.5.3.3 Versionshantering av konstanter och statiska skrivskyddade fält
Konstanter och readonly-fält har olika semantik för binär versionshantering. När ett uttryck refererar till en konstant, hämtas värdet för konstanten vid kompileringstidpunkt, men när ett uttryck refererar till ett skrivskyddat fält, hämtas inte värdet för fältet förrän vid körningstid.
Exempel: Överväg ett program som består av två separata program:
namespace Program1 { public class Utils { public static readonly int x = 1; } }
och
namespace Program2 { class Test { static void Main() { Console.WriteLine(Program1.Utils.X); } } }
Namnrymderna
Program1
ochProgram2
anger två program som kompileras separat. EftersomProgram1.Utils.X
deklareras som ettstatic readonly
-fält är värdet som genereras avConsole.WriteLine
-instruktionen inte känt vid kompileringstidpunkt, utan erhålls istället vid körningstid. Om värdetX
för ändras ochProgram1
omkompileras kommer instruktionenConsole.WriteLine
därför att mata ut det nya värdet även omProgram2
det inte är omkompilerat. Men om det hadeX
varit en konstant skulle värdetX
för ha erhållits vid den tidpunktenProgram2
kompilerades och skulle förbli opåverkad av ändringar iProgram1
tillsProgram2
omkompileras.slutexempel
15.5.4 Flyktiga fält
När en field_declaration innehåller en volatile
modifierare, är de fält som introduceras av den deklarationen volatila fält. För icke-flyktiga fält kan optimeringstekniker som ändrar ordning på instruktioner leda till oväntade och oförutsägbara resultat i program med flera trådar som kommer åt fält utan synkronisering, till exempel de som tillhandahålls av lock_statement (§13.13). Dessa optimeringar kan utföras av kompilatorn, av körningssystemet eller av maskinvaran. För flyktiga fält är sådana omordningsoptimeringar begränsade:
- En läsning av ett flyktigt fält kallas för en flyktig läsning. En flyktig läsning har "infångningssemantik"; det vill säga, det är garanterat att inträffa innan några minnesreferenser som är efterföljande i instruktionssekvensen.
- En skrivning av ett flyktigt fält kallas för en flyktig skrivning. En volatil skrivning har "release-semantik"; det vill säga att den garanteras inträffa efter alla minnesreferenser före skrivinstruktionen i instruktionssekvensen.
Dessa begränsningar säkerställer att alla trådar observerar volatila skrivningar som utförs av andra trådar i den ordning de har gjorts. En överensstämmande implementering är inte skyldig att tillhandahålla en enda total ordning av flyktiga skrivningar sett från alla exekveringstrådar. Typen av ett flyktigt fält ska vara något av följande:
- En referenstyp.
- En type_parameter som är känd för att vara en referenstyp (§15.2.5).
- Typen
byte
,sbyte
,short
,ushort
,int
,uint
,char
,float
,bool
,System.IntPtr
ellerSystem.UIntPtr
. - En enum_type som har en enum_base typ av
byte
,sbyte
,short
,ushort
,int
elleruint
.
Exempel: Exemplet
class Test { public static int result; public static volatile bool finished; static void Thread2() { result = 143; finished = true; } static void Main() { finished = false; // Run Thread2() in a new thread new Thread(new ThreadStart(Thread2)).Start(); // Wait for Thread2() to signal that it has a result // by setting finished to true. for (;;) { if (finished) { Console.WriteLine($"result = {result}"); return; } } } }
genererar utdata:
result = 143
I det här exemplet startar metoden
Main
en ny tråd som kör metodenThread2
. Den här metoden lagrar ett värde i ett icke-flyktigt fält med namnetresult
och lagrartrue
sedan i det flyktiga fältetfinished
. Huvudtråden väntar tills fältetfinished
har angetts tilltrue
och läser sedan fältetresult
. Eftersomfinished
har deklareratsvolatile
ska huvudtråden läsa värdet143
från fältetresult
. Om fältetfinished
inte hade deklareratsvolatile
, skulle det vara tillåtet för lagringen tillresult
att vara synlig för huvudtråden efter lagringen tillfinished
, och därmed för huvudtråden att läsa värdet 0 från fältetresult
. Om du deklarerarfinished
som ettvolatile
fält förhindras sådan inkonsekvens.slutexempel
15.5.5 Fältinitialisering
Det initiala värdet för ett fält, oavsett om det är ett statiskt fält eller ett instansfält, är standardvärdet (§9.3) av fältets typ. Det går inte att observera värdet för ett fält innan den här standardinitieringen har inträffat, och ett fält är därför aldrig "onitialiserat".
Exempel: Exemplet
class Test { static bool b; int i; static void Main() { Test t = new Test(); Console.WriteLine($"b = {b}, i = {t.i}"); } }
genererar utdata
b = False, i = 0
eftersom
b
ochi
båda initieras automatiskt till standardvärden.slutexempel
15.5.6 Variabelinitierare
15.5.6.1 Allmänt
Fältdeklarationer kan innehålla variable_initializers. För statiska fält motsvarar variabelinitierare tilldelningsinstruktioner som körs under klassinitiering. För fält gäller att variabelinitieringar motsvarar tilldelningsuttryck som utförs när en instans av klassen skapas.
Exempel: Exemplet
class Test { static double x = Math.Sqrt(2.0); int i = 100; string s = "Hello"; static void Main() { Test a = new Test(); Console.WriteLine($"x = {x}, i = {a.i}, s = {a.s}"); } }
genererar utdata
x = 1.4142135623730951, i = 100, s = Hello
eftersom en tilldelning till
x
inträffar när statiska fältinitierare körs och tilldelningar tilli
ochs
inträffar när instansfältinitierare körs.slutexempel
Standardvärdets initiering som beskrivs i §15.5.5 sker för alla fält, inklusive fält som har variabelinitierare. När en klass initieras initieras därför alla statiska fält i den klassen först till standardvärdena och sedan körs de statiska fältinitierarna i textordning. På samma sätt initieras alla instansfält i den instansen till standardvärdena när en instans av en klass skapas, och sedan körs instansfältinitierarna i textordning. När det finns fältdeklarationer i flera partiella typdeklarationer för samma typ är ordningen på delarna ospecificerad. Dock körs fältinitierare i ordning inom varje del.
Det är möjligt att statiska fält med variabelinitierare observeras i deras standardvärdetillstånd.
Exempel: Detta avråds dock starkt från som en fråga om stil. Exemplet
class Test { static int a = b + 1; static int b = a + 1; static void Main() { Console.WriteLine($"a = {a}, b = {b}"); } }
uppvisar detta beteende. Trots cirkulära definitioner av
a
ochb
är programmet giltigt. Det resulterar i utdataa = 1, b = 2
eftersom de statiska fälten
a
ochb
initieras till0
(standardvärdet förint
) innan deras initialiserare körs. När initialiseringen föra
körs är värdet avb
noll, och därför initialiserasa
till1
. När initiatorn förb
körs, är värdet av a redan1
, och därför initierasb
till2
.slutexempel
15.5.6.2 Initiering av statiskt fält
Variabelinitierare för statiska fält för en klass motsvarar en sekvens av tilldelningar som körs i textordningen där de visas i klassdeklarationen (§15.5.6.1). Inom en partiell klass anges innebörden av "textordning" av §15.5.6.1. Om det finns en statisk konstruktor (§15.12) i klassen sker körningen av de statiska fältinitierarna omedelbart innan den statiska konstruktorn körs. Annars körs de statiska fältinitierarna vid en implementeringsberoende tidpunkt innan ett statiskt fält i den klassen används första gången.
Exempel: Exemplet
class Test { static void Main() { Console.WriteLine($"{B.Y} {A.X}"); } public static int F(string s) { Console.WriteLine(s); return 1; } } class A { public static int X = Test.F("Init A"); } class B { public static int Y = Test.F("Init B"); }
kan producera antingen resultatet:
Init A Init B 1 1
eller utdata:
Init B Init A 1 1
eftersom körningen av
X
:s initializer ochY
:s initializer kan ske i vilken som helst ordning; de är endast begränsade till att ske före referenser till de fälten. Men i exemplet:class Test { static void Main() { Console.WriteLine($"{B.Y} {A.X}"); } public static int F(string s) { Console.WriteLine(s); return 1; } } class A { static A() {} public static int X = Test.F("Init A"); } class B { static B() {} public static int Y = Test.F("Init B"); }
Utdata skall vara:
Init B Init A 1 1
eftersom reglerna för när statiska konstruktorer körs (enligt definitionen i §15.12) anger att
B
's statiska konstruktor (och därmedB
's statiska fältinitierare) ska köras föreA
's statiska konstruktor och fältinitierare.slutexempel
15.5.6.3 Initialisering av instansfält
Instansfältvariabelinitierare för en klass motsvarar en sekvens av tilldelningar som körs omedelbart vid inmatning till någon av instanskonstruktörerna (§15.11.3) för den klassen. Inom en partiell klass anges innebörden av "textordning" av §15.5.6.1. Variabelinitierarna körs i textordningen där de visas i klassdeklarationen (§15.5.6.1). Processen för att skapa och initiera klassinstanser beskrivs ytterligare i §15.11.
En variabelinitierare för ett instansfält kan inte referera till den instans som skapas. Det är alltså ett kompileringsfel att referera this
till i en variabelinitierare, eftersom det är ett kompileringsfel för en variabelinitierare som refererar till alla instansmedlemmar via en simple_name.
Exempel: I följande kod
class A { int x = 1; int y = x + 1; // Error, reference to instance member of this }
variabelinitieraren för
y
resulterar i ett kompileringsfel eftersom den refererar till en medlem i instansen som skapas.slutexempel
15.6 Metoder
15.6.1 Allmänt
En metod är en medlem som implementerar en beräkning eller åtgärd som kan utföras av ett objekt eller en klass. Metoder deklareras med method_declaration s:
method_declaration
: attributes? method_modifiers return_type method_header method_body
| attributes? ref_method_modifiers ref_kind ref_return_type method_header
ref_method_body
;
method_modifiers
: method_modifier* 'partial'?
;
ref_kind
: 'ref'
| 'ref' 'readonly'
;
ref_method_modifiers
: ref_method_modifier*
;
method_header
: member_name '(' parameter_list? ')'
| member_name type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause*
;
method_modifier
: ref_method_modifier
| 'async'
;
ref_method_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| 'readonly' // direct struct members only
| unsafe_modifier // unsafe code support
;
return_type
: ref_return_type
| 'void'
;
ref_return_type
: type
;
member_name
: identifier
| interface_type '.' identifier
;
method_body
: block
| '=>' null_conditional_invocation_expression ';'
| '=>' expression ';'
| ';'
;
ref_method_body
: block
| '=>' 'ref' variable_reference ';'
| ';'
;
Grammatikanteckningar:
- unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).
- När man känner igen en method_body, om både null_conditional_invocation_expression och expression alternativ är tillämpliga, ska det förstnämnda väljas.
Obs! Överlappningen av och prioriteten mellan alternativen här är enbart för beskrivande bekvämlighet. Grammatikreglerna kan utarbetas för att ta bort överlappningen. ANTLR och andra grammatiksystem använder samma bekvämlighet, så method_body har de angivna semantiken automatiskt. slutkommentar
En method_declaration kan innehålla en uppsättning attribut (§22) och en av de tillåtna typerna av deklarerad tillgänglighet (§15.3.6), new
(§15.3.5), static
(§15.6.3), virtual
(§1 5.6.4), override
(§15.6.5), sealed
(§15.6.6), abstract
(§15.6.7), extern
(§15.6.8) och async
(§15.14). Dessutom kan en method_declaration som finns direkt av en struct_declaration inkludera readonly
modifieraren (§16.4.12).
En deklaration har en giltig kombination av modifierare om allt av följande är sant:
- Deklarationen innehåller en giltig kombination av åtkomstmodifierare (§15.3.6).
- Deklarationen innehåller inte samma modifierare flera gånger.
- Deklarationen innehåller högst en av följande modifierare:
static
,virtual
ochoverride
. - Deklarationen innehåller högst en av följande modifierare:
new
ochoverride
. - Om deklarationen
abstract
innehåller modifieraren innehåller deklarationen inte någon av följande modifierare:static
,virtual
,sealed
ellerextern
. - Om deklarationen
private
innehåller modifieraren innehåller deklarationen inte någon av följande modifierare:virtual
,override
ellerabstract
. - Om deklarationen innehåller modifieraren
sealed
, så innehåller deklarationen också modifierarenoverride
. - Om deklarationen
partial
innehåller modifieraren så innehåller den inte någon av de följande modifierarna:new
,public
,protected
,internal
,private
,virtual
,sealed
,override
,abstract
ellerextern
.
Metoderna klassificeras enligt vad, om något, de returnerar:
- Om
ref
finns är metoden returns-by-ref och returnerar en variabelreferens, som eventuellt kan vara skrivskyddad. - Annars, om return_type är
void
, då är metoden returns-no-value och den returnerar inte ett värde. - Annars är metoden returns-by-value och returnerar ett värde.
Den return_type av en metoddeklaration som returnerar genom värde eller returnerar inget värde anger vilken typ av resultat som, om det finns något, returneras av metoden. Endast en metod som inte returnerar ett värde får innehålla partial
modifieraren (§15.6.9). Om deklarationen async
innehåller modifieraren ska return_type vara void
eller metoden returnerar per värde och returtypen är en uppgiftstyp (§15.14.1).
ref_return_type för en metoddeklaration som returnerar via referens anger typen av variabeln som refereras av variable_reference som returneras av metoden.
En allmän metod är en metod vars deklaration innehåller en type_parameter_list. Detta anger typparametrarna för metoden. De valfria type_parameter_constraints_clause specificerar begränsningarna för typparametrarna.
En generisk method_declaration för en explicit implementering av en gränssnittsmedlem får inte ha några type_parameter_constraints_clause; deklarationen ärver eventuella begränsningar från begränsningarna för gränssnittsmetoden.
På samma sätt ska en metoddeklaration med override
modifieraren inte ha några type_parameter_constraints_clauseoch begränsningarna för metodens typparametrar ärvs från den virtuella metod som åsidosätts.
member_name anger namnet på metoden. Om inte metoden är en explicit implementering av gränssnittsmedlemmar (§18.6.2) är member_name helt enkelt en identifierare.
För en explicit implementering av gränssnittsmedlemmar består member_name av en interface_type följt av ett ".
" och en identifierare. I detta fall får deklarationen inte innehålla några andra modifierare än (möjligen) extern
eller async
.
Den valfria parameter_list anger metodens parametrar (§15.6.2).
return_type eller ref_return_type, och var och en av de typer som anges i parameter_list av en metod, ska vara minst lika tillgänglig som själva metoden (§7.5.5).
Den method_body av en metod som returnerar ett värde eller inget värde är antingen ett semikolon, en blockkropp eller en uttryckskropp. En blocktext består av ett block som anger vilka instruktioner som ska köras när metoden anropas. En uttryckstext består av =>
, följt av en null_conditional_invocation_expression eller ett uttryck och ett semikolon, och anger ett enda uttryck som ska utföras när metoden anropas.
För abstrakta och externa metoder består method_body helt enkelt av ett semikolon. För partiella metoder kan method_body bestå av antingen ett semikolon, en blockkropp eller en uttryckstext. För alla andra metoder är method_body antingen en blocktext eller en uttryckstext.
Om method_body består av semikolon ska deklarationen inte innehålla async
modifieraren.
Ref_method_body för en metod som returnerar med referens är antingen ett semikolon, en blockkropp eller en uttryckskropp. En blocktext består av ett block som anger vilka instruktioner som ska köras när metoden anropas. En uttryckstext består av =>
, följt av ref
, en variable_reference och ett semikolon, och anger en enda variable_reference för att utvärdera när metoden anropas.
För abstrakta och externa metoder består ref_method_body helt enkelt av ett semikolon. För alla andra metoder är ref_method_body antingen en blocktext eller en uttryckstext.
Namnet, antalet typparametrar och parameterlistan för en metod definierar metodens signatur (§7.6). Mer specifikt består signaturen för en metod av dess namn, antalet av dess typparametrar och antalet, parameter_mode_modifiers (§15.6.2.1) och typer av dess parametrar. Returtypen är inte en del av en metods signatur, inte heller namnen på parametrarna, namnen på typparametrarna eller begränsningarna. När en parametertyp refererar till en typparameter för metoden används ordningspositionen för typparametern (inte namnet på typparametern) för typjämförelse.
Namnet på en metod ska skilja sig från namnen på alla andra icke-metoder som deklareras i samma klass. Dessutom ska signaturen för en metod skilja sig från signaturerna för alla andra metoder som deklarerats i samma klass, och två metoder som deklareras i samma klass får inte ha signaturer som endast skiljer sig åt med in
, out
och ref
.
Metodens type_parameterfinns i omfånget i hela method_declaration och kan användas för att skapa typer i hela omfånget i return_type eller ref_return_type, method_body eller ref_method_body och type_parameter_constraints_clausemen inte i attribut.
Alla parametrar och typparametrar ska ha olika namn.
15.6.2 Metodparametrar
15.6.2.1 Allmänt
Parametrarna för en eventuell metod deklareras av metodens parameter_list.
parameter_list
: fixed_parameters
| fixed_parameters ',' parameter_array
| parameter_array
;
fixed_parameters
: fixed_parameter (',' fixed_parameter)*
;
fixed_parameter
: attributes? parameter_modifier? type identifier default_argument?
;
default_argument
: '=' expression
;
parameter_modifier
: parameter_mode_modifier
| 'this'
;
parameter_mode_modifier
: 'ref'
| 'out'
| 'in'
;
parameter_array
: attributes? 'params' array_type identifier
;
Parameterlistan består av en eller flera kommaavgränsade parametrar där endast den sista kan vara en parameter_array.
En fixed_parameter består av en valfri uppsättning attribut (§22); en valfri in
, out
, ref
, eller this
modifierare, en typ, en identifierare och en valfri default_argument. Varje fixed_parameter deklarerar en parameter av den angivna typen med det angivna namnet. Modifieraren this
anger metoden som en tilläggsmetod och tillåts endast för den första parametern för en statisk metod i en icke-generisk, icke-kapslad statisk klass. Om parametern är en struct
typ eller en typparameter som är begränsad till en struct
this
kan modifieraren kombineras med antingen ref
eller in
modifieraren, men inte out
modifieraren. Tilläggsmetoder beskrivs ytterligare i §15.6.10. En fixed_parameter med en default_argument kallas för en valfri parameter, medan en fixed_parameter utan default_argumentär en obligatorisk parameter. En obligatorisk parameter ska inte visas efter en valfri parameter i en parameter_list.
En parameter med en ref
, out
eller this
-modifierare kan inte ha en default_argument. En indataparameter kan ha en default_argument.
Uttrycket i en default_argument skall vara något av följande:
- en konstant_uttryck
- ett uttryck för formuläret
new S()
därS
är en värdetyp - ett uttryck för formuläret
default(S)
därS
är en värdetyp
Uttrycket ska implicit konverteras genom en identitets- eller nullbarhetskonvertering till typen av parametern.
Om valfria parametrar inträffar i en implementering av partiell metoddeklaration (§15.6.9), en explicit implementering av gränssnittsmedlemmar (§18.6.2), en indexerardeklaration med en parameter (§15.9), eller i en operatörsdeklaration (§15.10.1) bör en kompilator ge en varning, eftersom dessa medlemmar aldrig kan åberopas på ett sätt som tillåter att argument utelämnas.
En parameter_array består av en valfri uppsättning attribut (§22), en params
modifierare, en array_type och en identifierare. En parametermatris deklarerar en enskild parameter av den angivna matristypen med det angivna namnet.
array_type för en parametermatris ska vara en endimensionell matristyp (§17.2). I ett metodanrop tillåter en parametermatris att antingen ett enda argument av den angivna matristypen anges, eller så kan noll eller fler argument av matriselementtypen anges. Parametermatriser beskrivs ytterligare i §15.6.2.4.
En parameter_array kan inträffa efter en valfri parameter, men kan inte ha ett standardvärde – utelämnandet av argument för en parameter_array skulle i stället leda till att en tom matris skapas.
Exempel: Följande illustrerar olika typer av parametrar:
void M<T>( ref int i, decimal d, bool b = false, bool? n = false, string s = "Hello", object o = null, T t = default(T), params int[] a ) { }
I parameter_list för
M
äri
en obligatoriskref
parameter,d
är en obligatorisk värdeparameter, medanb
,s
,o
ocht
är valfria värdeparametrar ocha
är en parametermatris.slutexempel
En metoddeklaration skapar ett separat deklarationsutrymme (§7.3) för parametrar och typparametrar. Namn introduceras i det här deklarationsutrymmet av typparameterlistan och parameterlistan för metoden. Metodens brödtext, om det finns någon, betraktas som inbäddad i detta deklarationsutrymme. Det är ett fel att två medlemmar i ett metoddeklarationsutrymme har samma namn.
Ett metodanrop (§12.8.10.2) skapar en kopia, specifik för anropet, av parametrarna och de lokala variablerna för metoden, och argumentlistan över anropet tilldelar värden eller variabelreferenser till de nyligen skapade parametrarna. Inom blocket av en metod kan parametrar refereras till med sina identifierare i simple_name-uttryck (§12.8.4).
Följande typer av parametrar finns:
- Värdeparametrar (§15.6.2.2).
- Indataparametrar (§15.6.2.3.2).
- Utdataparametrar (§15.6.2.3.4).
- Referensparametrar (§15.6.2.3.3).
- Parametermatriser (§15.6.2.4).
Obs! Enligt beskrivningen i §7.6 är
in
,out
, ochref
modifierare en del av en metods signatur, menparams
är inte det. slutkommentar
15.6.2.2 Värdeparametrar
En parameter som deklareras utan modifierare är en värdeparameter. En värdeparameter är en lokal variabel som hämtar sitt ursprungliga värde från motsvarande argument som anges i metodanropet.
För bestämda tilldelningsregler, se §9.2.5.
Motsvarande argument i ett metodanrop ska vara ett uttryck som implicit är konvertibelt (§10.2) till parametertypen.
En metod tillåts tilldela nya värden till en värdeparameter. Sådana tilldelningar påverkar bara den lokala lagringsplats som representeras av värdeparametern – de har ingen effekt på det faktiska argumentet som anges i metodanropet.
15.6.2.3 Bireferensparametrar
15.6.2.3.1 Allmänt
Indata-, utdata- och referensparametrar är referensparametrar. En bireferensparameter är en lokal referensvariabel (§9.7); den första referensen hämtas från motsvarande argument som anges i metodanropet.
Obs! Referensen för en bireferensparameter kan ändras med referenstilldelningsoperatorn (
= ref
).
När en parameter är en referensparameter ska motsvarande argument i ett metodanrop bestå av det motsvarande nyckelordet, in
, ref
, eller out
, följt av en variabelreferens (§9.5) av samma typ som parametern. Men när parametern är en in
parameter kan argumentet vara ett uttryck för vilket en implicit konvertering (§10.2) finns från det argumentuttrycket till typen av motsvarande parameter.
Bireferensparametrar tillåts inte för funktioner som deklareras som iterator (§15.15) eller asynkron funktion (§15.14).
I en metod som tar flera bireferensparametrar är det möjligt att flera namn representerar samma lagringsplats.
15.6.2.3.2 Inmatningsparametrar
En parameter som deklareras med en in
-modifierare är en indataparameter. Argumentet som motsvarar en indataparameter är antingen en variabel som finns vid tidpunkten för metodanropet, eller en som skapats av implementeringen (§12.6.2.3) i metodens anrop. För bestämda tilldelningsregler, se §9.2.8.
Det är ett kompileringsfel att ändra värdet för en indataparameter.
Obs! Det primära syftet med indataparametrar är för effektivitet. När typen av en metodparameter är en stor struct (vad gäller minnesbehov) är det användbart att kunna undvika att kopiera hela värdet för argumentet när du anropar metoden. Med indataparametrar kan metoder referera till befintliga värden i minnet, samtidigt som de skyddar mot oönskade ändringar av dessa värden. slutkommentar
Referensparametrar för 15.6.2.3.3
En parameter som deklareras med en ref
modifierare är en referensparameter. För regler för bestämd tilldelning, se §9.2.6.
Exempel: Exemplet
class Test { static void Swap(ref int x, ref int y) { int temp = x; x = y; y = temp; } static void Main() { int i = 1, j = 2; Swap(ref i, ref j); Console.WriteLine($"i = {i}, j = {j}"); } }
genererar utdata
i = 2, j = 1
För anropet av
Swap
iMain
, representerarx
i
ochy
representerarj
. Anropet har därför effekten att växla värdena påi
ochj
.slutexempel
Exempel: I följande kod
class A { string s; void F(ref string a, ref string b) { s = "One"; a = "Two"; b = "Three"; } void G() { F(ref s, ref s); } }
anropet av
F
iG
skickar en referens tills
för bådea
ochb
. För det anropet refererar alltså namnens
,a
ochb
alla till samma lagringsplats, och de tre tilldelningarna ändrar alla instansfältets
.slutexempel
För en struct
typ inom en instansmetod, instansåtkomstor (§12.2.1) eller instanskonstruktor med en konstruktorinitierare fungerar nyckelordet this
exakt som en referensparameter av typen struct (§12.8.14).
15.6.2.3.4 Utdataparametrar
En parameter som deklareras med en out
modifierare är en utgångsparameter. För bestämda tilldelningsregler, se §9.2.7.
En metod som deklareras som en partiell metod (§15.6.9) får inte ha utdataparametrar.
Obs! Utdataparametrar används vanligtvis i metoder som producerar flera returvärden. slutkommentar
Exempel:
class Test { static void SplitPath(string path, out string dir, out string name) { int i = path.Length; while (i > 0) { char ch = path[i - 1]; if (ch == '\\' || ch == '/' || ch == ':') { break; } i--; } dir = path.Substring(0, i); name = path.Substring(i); } static void Main() { string dir, name; SplitPath(@"c:\Windows\System\hello.txt", out dir, out name); Console.WriteLine(dir); Console.WriteLine(name); } }
Exemplet genererar utdata:
c:\Windows\System\ hello.txt
Observera att variablerna
dir
ochname
kan tas bort innan de skickas tillSplitPath
och att de anses vara definitivt tilldelade efter anropet.slutexempel
15.6.2.4 Parametermatriser
En parameter som deklareras med en params
modifierare är en parametermatris. Om en parameterlista innehåller en parametermatris ska den vara den sista parametern i listan och den ska vara av en endimensionell matristyp.
Exempel: Typerna
string[]
ochstring[][]
kan användas som typ av en parametermatris, men typenstring[,]
kan inte göra det. slutexempel
Obs! Det går inte att kombinera
params
modifieraren med modifierarnain
,out
ellerref
. slutkommentar
En parametermatris tillåter att argument anges på något av två sätt i ett metodanrop:
- Argumentet för en parametermatris kan vara ett enda uttryck som implicit kan konverteras (§10.2) till parametermatristypen. I det här fallet fungerar parametermatrisen exakt som en värdeparameter.
- Alternativt kan anropet ange noll eller fler argument för parametermatrisen, där varje argument är ett uttryck som implicit är konvertibelt (§10.2) till elementtypen för parametermatrisen. I det här fallet skapar anropet en instans av parametermatristypen med en längd som motsvarar antalet argument, initierar elementen i matrisinstansen med de angivna argumentvärdena och använder den nyligen skapade matrisinstansen som det faktiska argumentet.
Förutom att tillåta ett variabelt antal argument i ett anrop motsvarar en parametermatris exakt en värdeparameter (§15.6.2.2) av samma typ.
Exempel: Exemplet
class Test { static void F(params int[] args) { Console.Write($"Array contains {args.Length} elements:"); foreach (int i in args) { Console.Write($" {i}"); } Console.WriteLine(); } static void Main() { int[] arr = {1, 2, 3}; F(arr); F(10, 20, 30, 40); F(); } }
genererar utdata
Array contains 3 elements: 1 2 3 Array contains 4 elements: 10 20 30 40 Array contains 0 elements:
Den första anropet av
F
skickar helt enkelt matrisenarr
som en värdeparameter. Det andra anropet av F skapar automatiskt ett fyraelementint[]
med de angivna elementvärdena och skickar matrisinstansen som en värdeparameter. På samma sätt skapar den tredje anropet avF
ett nollelementint[]
och skickar instansen som en värdeparameter. Det andra och tredje anropet är exakt likvärdiga med att skriva:F(new int[] {10, 20, 30, 40}); F(new int[] {});
slutexempel
När du utför överbelastningsmatchning kan en metod med en parametermatris vara tillämplig, antingen i sin normala form eller i dess expanderade form (§12.6.4.2). Den expanderade formen av en metod är endast tillgänglig om metodens normala form inte är tillämplig och endast om en tillämplig metod med samma signatur som det expanderade formuläret inte redan har deklarerats i samma typ.
Exempel: Exemplet
class Test { static void F(params object[] a) => Console.WriteLine("F(object[])"); static void F() => Console.WriteLine("F()"); static void F(object a0, object a1) => Console.WriteLine("F(object,object)"); static void Main() { F(); F(1); F(1, 2); F(1, 2, 3); F(1, 2, 3, 4); } }
genererar utdata
F() F(object[]) F(object,object) F(object[]) F(object[])
I exemplet ingår redan två av de möjliga expanderade formerna av metoden med en parametermatris i klassen som vanliga metoder. Dessa expanderade former beaktas därför inte vid överladdningsmatchning, och det första och tredje metodanropet väljer således de vanliga metoderna. När en klass deklarerar en metod med en parametermatris är det inte ovanligt att även inkludera några av de expanderade formulären som vanliga metoder. På så sätt kan du undvika allokeringen av en matrisinstans som inträffar när en expanderad form av en metod med en parametermatris anropas.
slutexempel
En matris är en referenstyp, så värdet som skickas för en parametermatris kan vara
null
.Exempel: Exemplet:
class Test { static void F(params string[] array) => Console.WriteLine(array == null); static void Main() { F(null); F((string) null); } }
genererar utdata:
True False
Det andra anropet genererar
False
som det är likvärdigt medF(new string[] { null })
och skickar en matris som innehåller en enda null-referens.slutexempel
När typen av en parametermatris är object[]
uppstår en potentiell tvetydighet mellan metodens normala form och det expanderade formuläret för en enskild object
parameter. Orsaken till tvetydigheten är att en object[]
är implicit konvertibel för att skriva object
. Tvetyydigheten utgör inget problem, dock, eftersom den kan lösas genom att infoga en typkonvertering vid behov.
Exempel: Exemplet
class Test { static void F(params object[] args) { foreach (object o in args) { Console.Write(o.GetType().FullName); Console.Write(" "); } Console.WriteLine(); } static void Main() { object[] a = {1, "Hello", 123.456}; object o = a; F(a); F((object)a); F(o); F((object[])o); } }
genererar utdata
System.Int32 System.String System.Double System.Object[] System.Object[] System.Int32 System.String System.Double
I de första och sista anropen av
F
gäller den normala formen avF
eftersom det finns en implicit konvertering från argumenttypen till parametertypen (båda är av typenobject[]
). Därför väljer överlagringsmatchning den normala formen avF
, och argumentet skickas som en vanlig värdeparameter. I den andra och tredje anropen är den normala formen inteF
tillämplig eftersom det inte finns någon implicit konvertering från argumenttypen till parametertypen (typenobject
kan inte konverteras implicit till typenobject[]
). Den utökade formen avF
är dock tillämplig, så den väljs av överbelastningslösning. Därför skapas ett ett-elementobject[]
av anropet och det enskilda elementet i matrisen initieras med det angivna argumentvärdet (som i sig är en referens till enobject[]
).slutexempel
15.6.3 Statiska metoder och instansmetoder
När en metoddeklaration innehåller en static
modifierare sägs den metoden vara en statisk metod. När det inte finns någon static
modifierare sägs metoden vara en instansmetod.
En statisk metod fungerar inte på en specifik instans och det är ett kompileringsfel att referera till this
i en statisk metod.
En instansmetod fungerar på en viss instans av en klass och den instansen kan nås som this
(§12.8.14).
Skillnaderna mellan statiska medlemmar och instansmedlemmar diskuteras ytterligare i §15.3.8.
15.6.4 Virtuella metoder
När en instansmetoddeklaration innehåller en virtuell modifierare sägs den metoden vara en virtuell metod. När det inte finns någon virtuell modifierare sägs metoden vara en icke-virtuell metod.
Implementeringen av en icke-virtuell metod är invariant: Implementeringen är densamma oavsett om metoden anropas på en instans av klassen där den deklareras eller en instans av en härledd klass. Implementeringen av en virtuell metod kan däremot ersättas av härledda klasser. Processen att ersätta implementeringen av en ärvd virtuell metod kallas åsidosättande av metoden (§15.6.5).
Vid ett virtuellt metodanrop avgör instansens körtidstyp vilken faktisk metodimplementering som ska anropas. I en icke-virtuell metodanrop är kompileringstidstypen för instansen den avgörande faktorn. När en metod med namnet N
anropas med en argumentlista A
på en instans med en kompileringstyp C
och en körningstyp R
(där R
är antingen C
eller en klass härledd från C
) bearbetas anropet enligt följande:
- Vid bindningstid tillämpas överbelastningslösning på
C
,N
ochA
för att välja en specifik metodM
från den uppsättning metoder som deklareras i och ärvs avC
. Detta beskrivs i §12.8.10.2. - Sedan vid körtid:
- Om
M
är en icke-virtuell metodM
anropas. - Annars är
M
en virtuell metod, och den mest härledda implementeringen avM
med avseende påR
anropas.
- Om
För varje virtuell metod som deklareras i eller ärvs av en klass finns det en mest härledd implementering av metoden med avseende på den klassen. Den mest härledda implementeringen av en virtuell metod M
med avseende på en klass R
bestäms på följande sätt:
- Om
R
innehåller den virtuella deklarationen avM
, är detta den fullständigt härledda implementeringen avM
med avseende påR
. - Annars, om
R
innehåller en åsidosättning avM
, är detta den mest härledda implementeringen avM
med avseende påR
. - Annars är den mest härledda implementeringen av
M
med avseendeR
på samma som den mest härledda implementeringen avM
med avseende på den direkta basklassen förR
.
Exempel: I följande exempel visas skillnaderna mellan virtuella och icke-virtuella metoder:
class A { public void F() => Console.WriteLine("A.F"); public virtual void G() => Console.WriteLine("A.G"); } class B : A { public new void F() => Console.WriteLine("B.F"); public override void G() => Console.WriteLine("B.G"); } class Test { static void Main() { B b = new B(); A a = b; a.F(); b.F(); a.G(); b.G(); } }
I exemplet
A
introducerar en icke-virtuell metodF
och en virtuell metodG
. KlassenB
introducerar en ny icke-virtuell metodF
, vilket döljer den ärvdaF
och åsidosätter även den ärvda metodenG
. Exemplet genererar utdata:A.F B.F B.G B.G
Observera att påståendet
a.G()
anroparB.G
, inteA.G
. Det beror på att körningstypen för instansen (som ärB
), inte kompileringstidstypen för instansen (som ärA
), avgör den faktiska metodimplementeringen som ska anropas.slutexempel
Eftersom metoder tillåts dölja ärvda metoder är det möjligt för en klass att innehålla flera virtuella metoder med samma signatur. Detta utgör inte ett tvetydighetsproblem, eftersom alla utom den mest härledda metoden är dolda.
Exempel: I följande kod
class A { public virtual void F() => Console.WriteLine("A.F"); } class B : A { public override void F() => Console.WriteLine("B.F"); } class C : B { public new virtual void F() => Console.WriteLine("C.F"); } class D : C { public override void F() => Console.WriteLine("D.F"); } class Test { static void Main() { D d = new D(); A a = d; B b = d; C c = d; a.F(); b.F(); c.F(); d.F(); } }
klasserna
C
ochD
innehåller två virtuella metoder med samma signatur: Den som introducerades avA
och den som introducerades avC
. Metoden som introducerades medC
döljer metoden som ärvts frånA
. Därför åsidosätter åsidosättningsdeklarationen iD
metoden som introducerades avC
, och det går inteD
att åsidosätta metoden som introducerades avA
. Exemplet genererar utdata:B.F B.F D.F D.F
Observera att det är möjligt att anropa den dolda virtuella metoden genom att komma åt en instans av
D
via en mindre härledd typ där metoden inte är dold.slutexempel
15.6.5 Åsidosättningsmetoder
När en instansmetoddeklaration innehåller en override
modifierare sägs metoden vara en åsidosättningsmetod. En åsidosättningsmetod åsidosätter en ärvd virtuell metod med samma signatur. En virtuell metoddeklaration introducerar en ny metod, men en deklaration av åsidosättningsmetoden specialiserar sig på en befintlig ärvd virtuell metod genom att tillhandahålla en ny implementering av den metoden.
Metoden som åsidosätts av en åsidosättningsdeklaration kallas den åsidosatta basmetoden För en åsidosättningsmetod M
som deklarerats i en klass C
bestäms den åsidosatta basmetoden genom att undersöka varje basklass för C
, med början i den direkta basklassen C
och fortsätter med varje efterföljande direkt basklass, tills minst en tillgänglig metod finns i en viss basklasstyp som har samma signatur som M
efter ersättning av typargument. För att hitta den åsidosatta basmetoden anses en metod vara tillgänglig om den är public
, om den är protected
, om den är protected internal
, eller om den antingen internal
är eller private protected
och deklareras i samma program som C
.
Ett kompileringsfel inträffar om inte alla följande gäller för en åsidosättningsdeklaration:
- En åsidosatt basmetod kan lokaliseras på det ovan beskrivna sättet.
- Det finns exakt en sådan åsidosatt basmetod. Den här begränsningen gäller endast om basklasstypen är en konstruerad typ där ersättningen av typargument gör signaturen för två metoder densamma.
- Den åsidosatta basmetoden är en virtuell, abstrakt eller överskriven metod. Med andra ord kan den åsidosatta basmetoden inte vara statisk eller icke-virtuell.
- Den åsidosatta basmetoden är inte en sluten metod.
- Det finns en identitetskonvertering mellan returtypen för den åsidosatta basmetoden och åsidosättningsmetoden.
- Åsidosättningsdeklarationen och den åsidosatta basmetoden har samma angivna åtkomsträttighet. Med andra ord kan en åsidosättningsdeklaration inte ändra tillgängligheten för den virtuella metoden. Men om den åsidosatta basmetoden är skyddad internt och den deklareras i en annan sammansättning än den sammansättning som innehåller åsidosättningsdeklarationen ska åsidosättningsdeklarationens deklarerade tillgänglighet skyddas.
- Åsidosättningsdeklarationen specificerar inga type_parameter_constraints_clause. I stället ärvs begränsningarna från den åsidosatta basmetoden. Begränsningar som är typparametrar i den åsidosatta metoden kan ersättas med typargument i den ärvda begränsningen. Detta kan leda till begränsningar som inte är giltiga när de uttryckligen anges, till exempel värdetyper eller förseglade typer.
Exempel: Följande visar hur de övergripande reglerna fungerar för generiska klasser:
abstract class C<T> { public virtual T F() {...} public virtual C<T> G() {...} public virtual void H(C<T> x) {...} } class D : C<string> { public override string F() {...} // Ok public override C<string> G() {...} // Ok public override void H(C<T> x) {...} // Error, should be C<string> } class E<T,U> : C<U> { public override U F() {...} // Ok public override C<U> G() {...} // Ok public override void H(C<T> x) {...} // Error, should be C<U> }
slutexempel
En åsidosättningsdeklaration kan komma åt den åsidosatta basmetoden med hjälp av en base_access (§12.8.15).
Exempel: I följande kod
class A { int x; public virtual void PrintFields() => Console.WriteLine($"x = {x}"); } class B : A { int y; public override void PrintFields() { base.PrintFields(); Console.WriteLine($"y = {y}"); } }
anropet
base.PrintFields()
iB
anropar metoden PrintFields som deklarerats iA
. En base_access inaktiverar den virtuella anropsmekanismen och behandlar helt enkelt basmetoden som en icke-metodvirtual
. Om anropet hade skrivits somB
, skulle det rekursivt anropa metoden((A)this).PrintFields()
som deklarerats iPrintFields
, inte den som deklarerats iB
, eftersomA
är virtuell och körningstypen avPrintFields
är((A)this)
.slutexempel
Endast genom att inkludera en override
modifierare kan en metod åsidosätta en annan metod. I alla andra fall döljer en metod med samma signatur som en ärvd metod helt enkelt den ärvda metoden.
Exempel: I följande kod
class A { public virtual void F() {} } class B : A { public virtual void F() {} // Warning, hiding inherited F() }
metoden
F
iB
innehåller ingenoverride
-modifierare och därför åsidosätter den inte metodenF
iA
.F
I stället döljer metoden iB
metoden iA
, och en varning rapporteras eftersom deklarationen inte innehåller någon ny modifierare.slutexempel
Exempel: I följande kod
class A { public virtual void F() {} } class B : A { private new void F() {} // Hides A.F within body of B } class C : B { public override void F() {} // Ok, overrides A.F }
metoden
F
iB
döljer den virtuellaF
metoden som ärvts frånA
. Eftersom den nyaF
iB
har privat åtkomst innehåller dess omfång endast klasstextenB
för och utökar inte tillC
. Därför är deklarationenF
iC
tillåten att åsidosätta den ärvdaF
frånA
.slutexempel
15.6.6 Förseglade metoder
När en instansmetoddeklaration innehåller en sealed
modifierare sägs den metoden vara en förseglad metod. En förseglad metod åsidosätter en ärvd virtuell metod med samma signatur. En förseglad metod ska också märkas med override
modifieraren. Med hjälp av sealed
modifieraren förhindras en härledd klass från att åsidosätta metoden ytterligare.
Exempel: Exemplet
class A { public virtual void F() => Console.WriteLine("A.F"); public virtual void G() => Console.WriteLine("A.G"); } class B : A { public sealed override void F() => Console.WriteLine("B.F"); public override void G() => Console.WriteLine("B.G"); } class C : B { public override void G() => Console.WriteLine("C.G"); }
klassen
B
innehåller två åsidosättningsmetoder: enF
metod som harsealed
modifieraren och enG
metod som inte gör det.B
's användning avsealed
modifikatorn förhindrarC
från vidare åsidosättning avF
.slutexempel
15.6.7 Abstrakta metoder
När en instansmetoddeklaration innehåller en abstract
modifierare sägs den metoden vara en abstrakt metod. Även om en abstrakt metod implicit också är en virtuell metod kan den inte ha modifieraren virtual
.
En abstrakt metoddeklaration introducerar en ny virtuell metod men tillhandahåller ingen implementering av den metoden. I stället måste icke-abstrakta härledda klasser själva implementera metoden genom att åsidosätta den. Eftersom en abstrakt metod inte ger någon faktisk implementering består metodtexten för en abstrakt metod helt enkelt av ett semikolon.
Abstrakta metoddeklarationer tillåts endast i abstrakta klasser (§15.2.2.2).
Exempel: I följande kod
public abstract class Shape { public abstract void Paint(Graphics g, Rectangle r); } public class Ellipse : Shape { public override void Paint(Graphics g, Rectangle r) => g.DrawEllipse(r); } public class Box : Shape { public override void Paint(Graphics g, Rectangle r) => g.DrawRect(r); }
klassen
Shape
definierar den abstrakta uppfattningen om ett geometriskt formobjekt som kan måla sig själv. MetodenPaint
är abstrakt eftersom det inte finns någon meningsfull standardimplementering. KlassernaEllipse
ochBox
är konkretaShape
implementeringar. Eftersom dessa klasser inte är abstrakta måste de åsidosättaPaint
metoden och tillhandahålla en faktisk implementering.slutexempel
Det är ett kompileringsfel för en base_access (§12.8.15) att hänvisa till en abstrakt metod.
Exempel: I följande kod
abstract class A { public abstract void F(); } class B : A { // Error, base.F is abstract public override void F() => base.F(); }
ett kompileringsfel rapporteras för anropet
base.F()
eftersom det refererar till en abstrakt metod.slutexempel
En abstrakt metoddeklaration tillåts åsidosätta en virtuell metod. På så sätt kan en abstrakt klass framtvinga omimplementering av metoden i härledda klasser och göra den ursprungliga implementeringen av metoden otillgänglig.
Exempel: I följande kod
class A { public virtual void F() => Console.WriteLine("A.F"); } abstract class B: A { public abstract override void F(); } class C : B { public override void F() => Console.WriteLine("C.F"); }
klass
A
deklarerar en virtuell metod, klassenB
åsidosätter den här metoden med en abstrakt metod och klassenC
åsidosätter den abstrakta metoden för att tillhandahålla en egen implementering.slutexempel
15.6.8 Externa metoder
När en metoddeklaration innehåller en extern
modifierare sägs metoden vara en extern metod. Externa metoder implementeras externt, vanligtvis med ett annat språk än C#. Eftersom en extern metoddeklaration inte ger någon faktisk implementering består metodtexten för en extern metod helt enkelt av ett semikolon. En extern metod får inte vara generisk.
Den mekanism genom vilken kopplingen till en extern metod uppnås är implementeringsdefinierad.
Exempel: I följande exempel visas hur modifieraren och
extern
attributet användsDllImport
:class Path { [DllImport("kernel32", SetLastError=true)] static extern bool CreateDirectory(string name, SecurityAttribute sa); [DllImport("kernel32", SetLastError=true)] static extern bool RemoveDirectory(string name); [DllImport("kernel32", SetLastError=true)] static extern int GetCurrentDirectory(int bufSize, StringBuilder buf); [DllImport("kernel32", SetLastError=true)] static extern bool SetCurrentDirectory(string name); }
slutexempel
15.6.9 Partiella metoder
När en metoddeklaration innehåller en partial
modifierare sägs den metoden vara en partiell metod. Partiella metoder kan endast deklareras som medlemmar av partiella typer (§15.2.7) och omfattas av ett antal begränsningar.
Partiella metoder kan definieras i en del av en typdeklaration och implementeras i en annan. Implementeringen är valfri. Om ingen del implementerar den partiella metoden tas den partiella metoddeklarationen och alla anrop till den bort från typdeklarationen som följer av kombinationen av delarna.
Partiella metoder får inte definiera åtkomstmodifierare. de är implicit privata. Deras returtyp ska vara void
, och deras parametrar får inte vara utdataparametrar. Identifieraren partial
identifieras som ett kontextuellt nyckelord (§6.4.4) i en metoddeklaration endast om den visas omedelbart före nyckelordet void
. En partiell metod kan inte uttryckligen implementera gränssnittsmetoder.
Det finns två typer av partiella metoddeklarationer: Om brödtexten i metoddeklarationen är ett semikolon, sägs deklarationen vara en definierande partiell metoddeklaration. Om brödtexten är annan än ett semikolon, sägs deklarationen vara en genomförandedelmetoddeklaration. I delar av en typdeklaration ska det endast finnas en som definierar en partiell metoddeklaration med en viss signatur, och det får högst finnas en genomförandedeklaration för partiell metod med en given underskrift. Om en implementerande delmetodsdeklaration anges ska det finnas en motsvarande definierande delmetodsdeklaration och deklarationerna ska överensstämma enligt följande.
- Deklarationerna ska ha samma modifierare (men inte nödvändigtvis i samma ordning), metodnamn, antal typparametrar och antal parametrar.
- Motsvarande parametrar i deklarationerna ska ha samma modifierare (men inte nödvändigtvis i samma ordning) och samma typer, eller identitetskonverterbara typer (modulodifferenser i typparameternamn).
- Motsvarande typparametrar i deklarationerna ska ha samma begränsningar (moduloskillnader i typparameternamn).
En partiell metoddeklaration för implementering kan visas i samma del som motsvarande definierande partiell metoddeklaration.
Endast en definierande partiell metod deltar i överlagringsupplösning. Oavsett om en implementeringsdeklaration ges eller inte kan anropsuttryck leda till anrop av en partiell metod. Eftersom en partiell metod alltid returnerar void
är sådana anropsuttryck alltid uttrycksuttryck. Eftersom en partiell metod är implicit private
kommer sådana påståenden alltid att förekomma inom en av de delar av typdeklarationen där den partiella metoden deklareras.
Obs! Definitionen av matchande definition och implementering av partiella metoddeklarationer kräver inte att parameternamn matchar. Detta kan ge ett överraskande, om än väldefinierat, beteende när namngivna argument (§12.6.2.1) används. Till exempel, med tanke på den definierande partiella metoddeklarationen för
M
i en fil och implementeringen av partiell metoddeklaration i en annan fil:// File P1.cs: partial class P { static partial void M(int x); } // File P2.cs: partial class P { static void Caller() => M(y: 0); static partial void M(int y) {} }
är ogiltigt eftersom anropet använder argumentnamnet från implementeringen och inte den definierande partiella metoddeklarationen.
slutkommentar
Om ingen del av en partiell typdeklaration innehåller en implementeringsdeklaration för en viss partiell metod, tas alla uttrycksinstruktor som anropar den helt enkelt bort från den kombinerade typdeklarationen. Därför har anropsuttrycket, inklusive alla eventuella underuttryck, ingen effekt vid körning. Själva den partiella metoden tas också bort och kommer inte att vara medlem i den kombinerade typdeklarationen.
Om det finns en implementeringsdeklaration för en viss partiell metod behålls anropen för de partiella metoderna. Den partiella metoden ger upphov till en metoddeklaration som liknar den partiella metoddeklarationen för implementering med undantag för följande:
Modifieraren
partial
ingår inte.Attributen i den resulterande metoddeklarationen är de kombinerade attributen för den definierande och implementerande partiella metoddeklarationen i ospecificerad ordning. Dubbletter tas inte bort.
Attributen för parametrarna i den resulterande metoddeklarationen är de kombinerade attributen för motsvarande parametrar för definitionen och implementeringen av partiell metoddeklaration i ospecificerad ordning. Dubbletter tas inte bort.
Om en definierande deklaration men inte en implementeringsdeklaration anges för en partiell metod M
gäller följande begränsningar:
Det är ett kompileringstidfel att skapa en delegering från
M
(§12.8.17.5).Det är ett kompileringsfel att referera till
M
i en anonym funktion som konverteras till en uttrycksträdstyp (§8.6).Uttryck som inträffar som en del av ett anrop av
M
påverkar inte det bestämda tilldelningstillståndet (§9.4), vilket potentiellt kan leda till kompileringsfel.M
kan inte vara startpunkten för en ansökan (§7.1).
Partiella metoder är användbara för att tillåta att en del av en typdeklaration anpassar beteendet för en annan del, t.ex. en som genereras av ett verktyg. Överväg följande partiella klassdeklaration:
partial class Customer
{
string name;
public string Name
{
get => name;
set
{
OnNameChanging(value);
name = value;
OnNameChanged();
}
}
partial void OnNameChanging(string newName);
partial void OnNameChanged();
}
Om den här klassen kompileras utan några andra delar tas de definierande partiella metoddeklarationerna och deras anrop bort och den resulterande kombinerade klassdeklarationen motsvarar följande:
class Customer
{
string name;
public string Name
{
get => name;
set => name = value;
}
}
Anta dock att en annan del ges som tillhandahåller implementeringsdeklarationer av de partiella metoderna:
partial class Customer
{
partial void OnNameChanging(string newName) =>
Console.WriteLine($"Changing {name} to {newName}");
partial void OnNameChanged() =>
Console.WriteLine($"Changed to {name}");
}
Sedan motsvarar den resulterande kombinerade klassdeklarationen följande:
class Customer
{
string name;
public string Name
{
get => name;
set
{
OnNameChanging(value);
name = value;
OnNameChanged();
}
}
void OnNameChanging(string newName) =>
Console.WriteLine($"Changing {name} to {newName}");
void OnNameChanged() =>
Console.WriteLine($"Changed to {name}");
}
15.6.10 Tilläggsmetoder
När den första parametern för en metod innehåller this
modifieraren sägs den metoden vara en tilläggsmetod. Tilläggsmetoder ska endast deklareras i icke-generiska, icke-kapslade statiska klasser. Den första parametern för en tilläggsmetod är begränsad enligt följande:
- Det kan bara vara en indataparameter om den har en värdetyp
- Det kan bara vara en referensparameter om den har en värdetyp eller har en allmän typ som är begränsad till struct
- Det ska inte vara en pekartyp.
Exempel: Följande är ett exempel på en statisk klass som deklarerar två tilläggsmetoder:
public static class Extensions { public static int ToInt32(this string s) => Int32.Parse(s); public static T[] Slice<T>(this T[] source, int index, int count) { if (index < 0 || count < 0 || source.Length - index < count) { throw new ArgumentException(); } T[] result = new T[count]; Array.Copy(source, index, result, 0, count); return result; } }
slutexempel
En tilläggsmetod är en vanlig statisk metod. Där dess omslutande statiska klass finns i omfånget kan dessutom en tilläggsmetod anropas med instansmetodens anropssyntax (§12.8.10.3), med mottagaruttrycket som första argument.
Exempel: Följande program använder de tilläggsmetoder som deklareras ovan:
static class Program { static void Main() { string[] strings = { "1", "22", "333", "4444" }; foreach (string s in strings.Slice(1, 2)) { Console.WriteLine(s.ToInt32()); } } }
Metoden
Slice
är tillgänglig påstring[]
, ochToInt32
metoden är tillgänglig påstring
, eftersom de har deklarerats som tilläggsmetoder. Innebörden av programmet är densamma som följande, med vanliga statiska metodanrop:static class Program { static void Main() { string[] strings = { "1", "22", "333", "4444" }; foreach (string s in Extensions.Slice(strings, 1, 2)) { Console.WriteLine(Extensions.ToInt32(s)); } } }
slutexempel
15.6.11 Metodtext
Metodtexten i en metoddeklaration består av antingen en blocktext, en uttryckstext eller ett semikolon.
Abstrakta och externa metoddeklarationer tillhandahåller inte någon metodimplementering, så deras metodkroppar består helt enkelt av ett semikolon. För andra metoder är metodtexten ett block (§13.3) som innehåller de instruktioner som ska köras när metoden anropas.
Den effektiva returtypen för en metod är void
om returtypen är void
, eller om metoden är asynkron och returtypen är «TaskType»
(§15.14.1). Annars är den effektiva returtypen för en icke-asynkron metod dess returtyp och den effektiva returtypen för en asynkron metod med returtyp «TaskType»<T>
(§15.14.1) är T
.
När den effektiva returtypen för en metod är void
och metoden har en blockkropp, får return
-satser (§13.10.5) i blocket inte ange ett uttryck. Om körningen av blocket för en void-metod slutförs normalt (det vill säga att kontrollen flödar från slutet av metodkroppen), återgår metoden helt enkelt till anroparen.
När den effektiva returtypen för en metod är void
och metoden har en uttryckstext, ska uttrycket E
vara en statement_expression, och brödtexten är exakt likvärdig med en blocktext i formuläret { E; }
.
För en metod för avkastning per värde (§15.6.1) ska varje returutdrag i metodens brödtext ange ett uttryck som implicit är konvertibelt till den effektiva returtypen.
För en return-by-ref-metod (§15.6.1) ska varje returutdrag i metodens brödtext ange ett uttryck vars typ är av den effektiva returtypen och ha en referenssäker kontext av anroparkontext (§9.7.2).
För metoder som returnerar via värde och referens ska slutpunkten i metodkroppen inte vara nåbar. Med andra ord är det inte tillåtet för programflödet att fortsätta efter slutet av metodens kodblock.
Exempel: I följande kod
class A { public int F() {} // Error, return value required public int G() { return 1; } public int H(bool b) { if (b) { return 1; } else { return 0; } } public int I(bool b) => b ? 1 : 0; }
den värdeåtergivande
F
-metoden resulterar i ett kompileringsfel eftersom kontrollen kan flöda utanför slutet av metodkroppen. MetodernaG
ochH
är korrekta eftersom alla möjliga exekveringsvägar slutar med en retursats som specificerar ett returvärde. MetodenI
är korrekt eftersom dess kropp motsvarar ett block med endast en retursats i den.slutexempel
15.7 Egenskaper
15.7.1 Allmänt
En egenskap är en medlem som ger åtkomst till en egenskap för ett objekt eller en klass. Exempel på egenskaper är längden på en sträng, storleken på ett teckensnitt, beskrivningen av ett fönster och namnet på en kund. Egenskaper är en naturlig förlängning av fält – båda är namngivna medlemmar med associerade typer, och syntaxen för att komma åt fält och egenskaper är densamma. Till skillnad från fält anger egenskaperna dock inte lagringsplatser. Istället har egenskaper accessorer som anger de instruktioner som ska köras när deras värden läses eller skrivs. Egenskaper ger därmed en mekanism för att associera åtgärder med läsning och skrivning av ett objekts eller en klasss egenskaper. Dessutom tillåter de att sådana egenskaper beräknas.
Egenskaper deklareras med property_declaration s:
property_declaration
: attributes? property_modifier* type member_name property_body
| attributes? property_modifier* ref_kind type member_name ref_property_body
;
property_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| 'readonly' // direct struct members only
| unsafe_modifier // unsafe code support
;
property_body
: '{' accessor_declarations '}' property_initializer?
| '=>' expression ';'
;
property_initializer
: '=' variable_initializer ';'
;
ref_property_body
: '{' ref_get_accessor_declaration '}'
| '=>' 'ref' variable_reference ';'
;
unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).
En property_declaration kan innehålla en uppsättning attribut (§22) och någon av de tillåtna typerna av deklarerad tillgänglighet (§15.3.6), new
(§15.3.5), static
(§15.7.2), virtual
(§15.6.4, §15.7.6), override
(§15.6.5, §15.7.6), sealed
(§15.6.6), abstract
(§15.6.7, §15.7.6) och extern
(§15.6.8). Dessutom kan en property_declaration som finns direkt av en struct_declaration inkludera readonly
modifieraren (§16.4.11).
- Den första deklarerar en egenskap med ett icke-referensbaserat värde. Dess värde har typen type. Den här typen av egenskap kan vara läsbar och/eller skrivbar.
- Den andra deklarerar en egenskap som har referensvärde. Dess värde är en variabelreferens (§9.5), som kan vara
readonly
, till en variabel av typen typ. Den här typen av egenskap är endast läsbar.
En property_declaration kan innehålla en uppsättning attribut (§22) och någon av de tillåtna typerna av deklarerad tillgänglighet (§15.3.6), new
(§15.3.5), static
(§15.7.2), virtual
(§15.6.4, §15.7.6), override
(§15.6.5, §15.7.6), sealed
(§15.6.6), abstract
(§15.6.7, §15.7.6) och extern
(§15.6.8) modifierare.
Egenskapsdeklarationer omfattas av samma regler som metoddeklarationer (§15.6) när det gäller giltiga kombinationer av modifierare.
Member_name (§15.6.1) anger namnet på egenskapen. Om inte egenskapen är en explicit implementering av gränssnittsmedlemmar är member_name helt enkelt en identifierare. För en explicit implementering av gränssnittsmedlemmar (§18.6.2) består member_name av en interface_type följt av en ".
" och en identifierare.
Fastighetens typ ska vara minst lika tillgänglig som själva fastigheten (§7.5.5).
En property_body kan antingen bestå av en satstext eller en uttryckstext. I en satstext, där accessor_declarations ska omges av "{
" och "}
" tokens, deklareras accessorerna (§15.7.3) för egenskapen. Accessorerna specificerar de körbara instruktioner som är associerade med att läsa och skriva egenskapen.
I en property_body en uttryckstext som består av =>
följt av ett uttryckE
och ett semikolon är exakt likvärdigt med uttryckstexten { get { return E; } }
, och kan därför endast användas för att ange skrivskyddade egenskaper där resultatet av get-accessorn ges av ett enda uttryck.
En property_initializer får endast ges för en automatiskt implementerad egenskap (§15.7.4) och orsakar initieringen av det underliggande fältet för sådana egenskaper med det värde som anges av uttrycket.
En ref_property_body kan antingen bestå av ett instruktionsblock eller en uttryckstext. I en satskropp deklarerar en get_accessor_declaration get-accessorn (§15.7.3) av egenskapen. Accessorn specificerar de körbara instruktionerna som är kopplade till läsning av egenskapen.
I en ref_property_body består en uttryckskropp av =>
följt av ref
, en variable_referenceV
och ett semikolon som är exakt likvärdigt med statementskroppen { get { return ref V; } }
.
Obs! Även om syntaxen för att komma åt en egenskap är densamma som för ett fält klassificeras inte en egenskap som en variabel. Därför är det inte möjligt att skicka en egenskap som ett
in
,out
, ellerref
argument om inte egenskapen är ref-värde och därför returnerar en variabelreferens (§9.7). slutkommentar
När en egenskapsdeklaration innehåller en extern
modifierare sägs egenskapen vara en extern egenskap. Eftersom en extern egenskapsdeklaration inte tillhandahåller någon faktisk implementering ska var och en av de accessor_bodys i dess accessor_declarations vara semikolon.
15.7.2 Statiska egenskaper och instansegenskaper
När en egenskapsdeklaration innehåller en static
modifierare sägs egenskapen vara en statisk egenskap. När det inte finns någon static
modifierare sägs egenskapen vara en instansegenskap.
En statisk egenskap är inte associerad med en specifik instans, och det är ett kompileringsfel att referera till i åtkomsterna för this
en statisk egenskap.
En instansegenskap är associerad med en viss instans av en klass och den instansen kan nås som this
(§12.8.14) i egenskapens accessorer.
Skillnaderna mellan statiska medlemmar och instansmedlemmar diskuteras ytterligare i §15.3.8.
15.7.3 Åtkomstmetoder
Obs! Den här satsen gäller både egenskaper (§15.7) och indexerare (§15.9). Satsen är skriven i termer av egenskaper. Vid läsning för indexerare, ersätt indexerare/indexerare med egenskap/egenskaper och konsultera listan över skillnader mellan egenskaper och indexerare som finns i §15.9.2. slutkommentar
Accessor_declarations för en egenskap anger vilka körbara instruktioner som är kopplade till att skriva och/eller läsa egenskapen.
accessor_declarations
: get_accessor_declaration set_accessor_declaration?
| set_accessor_declaration get_accessor_declaration?
;
get_accessor_declaration
: attributes? accessor_modifier? 'get' accessor_body
;
set_accessor_declaration
: attributes? accessor_modifier? 'set' accessor_body
;
accessor_modifier
: 'protected'
| 'internal'
| 'private'
| 'protected' 'internal'
| 'internal' 'protected'
| 'protected' 'private'
| 'private' 'protected'
| 'readonly' // direct struct members only
;
accessor_body
: block
| '=>' expression ';'
| ';'
;
ref_get_accessor_declaration
: attributes? accessor_modifier? 'get' ref_accessor_body
;
ref_accessor_body
: block
| '=>' 'ref' variable_reference ';'
| ';'
;
Accessor_declarations består av en get_accessor_declaration, en set_accessor_declaration eller båda. Varje åtkomstdeklaration består av valfria attribut, en valfri accessor_modifier, token get
eller set
, följt av en accessor_body.
För en referensvärdesegenskap består ref_get_accessor_declaration av valfria attribut, en valfri accessor_modifier, token get
, följt av en ref_accessor_body.
Användningen av accessor_modifierstyrs av följande begränsningar:
- En accessor_modifier får inte användas i ett gränssnitt eller i en explicit gränssnittsmedlemsimplementering.
- accessor_modifier tillåts endast i en property_declaration eller indexer_declaration som direkt ingår i en struct_declaration (§16.4.11, §16.4.13).
- För en egenskap eller indexerare som inte har någon
override
modifierare tillåts endast en accessor_modifier om egenskapen eller indexeraren har både en get- och set-åtkomstor och sedan endast tillåts på någon av dessa åtkomstorer. - För en egenskap eller indexerare som innehåller en
override
-modifierare ska en accessor matcha accessor_modifier, om möjligt, hos den accessor som åsidosätts. -
Accessor_modifier ska deklarera en tillgänglighet som är strikt mer restriktiv än den deklarerade tillgängligheten för själva egendomen eller indexeraren. För att vara exakt:
- Om egenskapen eller indexeraren har en deklarerad tillgänglighet för
public
kan den tillgänglighet som deklareras av accessor_modifier vara antingenprivate protected
,protected internal
,internal
,protected
ellerprivate
. - Om egenskapen eller indexeraren har en deklarerad tillgänglighet för
protected internal
kan den tillgänglighet som deklareras av accessor_modifier vara antingenprivate protected
,protected private
,internal
,protected
ellerprivate
. - Om egenskapen eller indexeraren har en deklarerad tillgänglighet för
internal
ellerprotected
ska den tillgänglighet som deklareras av accessor_modifier vara antingenprivate protected
ellerprivate
. - Om egenskapen eller indexeraren har en deklarerad tillgänglighet för
private protected
ska den tillgänglighet som deklareras av accessor_modifier varaprivate
. - Om egenskapen eller indexeraren har en deklarerad tillgänglighet för
private
får ingen accessor_modifier användas.
- Om egenskapen eller indexeraren har en deklarerad tillgänglighet för
För abstract
och extern
icke-referensvärdesegenskaper är alla accessor_body för varje angiven accessor helt enkelt ett semikolon. En icke-abstrakt, icke-extern egenskap, men inte en indexerare, kan också ha accessor_body för alla angivna accessorer vara ett semikolon, i vilket fall det är en automatiskt implementerad egenskap (§15.7.4). En automatiskt implementerad egenskap ska ha minst en get-accessor. För accessorer till andra icke-abstrakta, icke-externa egenskaper är accessor_body antingen:
- ett block som anger vilka instruktioner som ska köras när motsvarande accessor anropas, eller
- en uttryckstext, som består av
=>
följt av ett uttryck och ett semikolon, och anger ett enda uttryck som ska köras när motsvarande accessor anropas.
För abstract
och extern
referensvärdesegenskaper är ref_accessor_body helt enkelt ett semikolon. För en accessor för någon annan icke-abstrakt, icke-extern egenskap är ref_accessor_body antingen:
- ett block som anger vilka instruktioner som ska köras när get-accessorn anropas, eller
- en uttryckstext, som består av
=>
följt avref
, en variable_reference och ett semikolon. Variabelreferensen utvärderas när get-accessorn anropas.
En get-accessor för en icke-referensvärdesegenskap motsvarar en parameterlös metod med ett returvärde för egenskapstypen. Förutom målet för en tilldelning anropas dess get-accessor när en sådan egenskap refereras till i ett uttryck för att beräkna värdet för egenskapen (§12.2.2).
En get-accessor för en icke-referensvärdeegenskap ska överensstämma med reglerna för metoder som returnerar värden, som beskrivs i §15.6.11. I synnerhet ska alla return
instruktioner i en get-accessor ange ett uttryck som implicit är konvertibelt till egenskapstypen. Dessutom ska slutpunkten för en get-accessor inte vara tillgänglig.
En get-accessor för en referensvärdesegenskap motsvarar en parameterlös metod med ett returvärde för en variable_reference till en variabel av egenskapstypen. När en sådan egenskap refereras i ett uttryck anropas dess get-accessor för att beräkna egenskapens variable_reference värde. Den variabelreferensen, precis som alla andra, används sedan för att läsa eller, för icke-skrivskyddade variabelreferenser, skriva den refererade variabeln enligt kontexten.
Exempel: I följande exempel visas en referensvärdesegenskap som mål för en tilldelning:
class Program { static int field; static ref int Property => ref field; static void Main() { field = 10; Console.WriteLine(Property); // Prints 10 Property = 20; // This invokes the get accessor, then assigns // via the resulting variable reference Console.WriteLine(field); // Prints 20 } }
slutexempel
Kroppen till en get-åtkomstmetod för en egenskap värderad som ref skall följa reglerna för ref-värderade metoder beskrivna i §15.6.11.
En set-accessor motsvarar en metod med en enda värdeparameter av egenskapens typ och en void
returtyp. Den implicita parametern för en uppsättningsåtkomstor heter alltid value
. När en egenskap refereras till som mål för en tilldelning (§12.21), eller som operande av eller ++
(–-
, §12.9.6), anropas den inställda accessorn med ett argument som ger det nya värdet (§12.21.2). Kroppen av en set-tilldelare ska följa reglerna för void
metoder som beskrivs i §15.6.11. I synnerhet får returneringssatser i set-accessorns metodkropp inte ange ett uttryck. Eftersom en uppsättningsåtkomst i sig har en parameter med namnet value
, är det ett kompileringsfel för en lokal variabel- eller konstantdeklaration i en uppsättningsåtkomst att ha det namnet.
Baserat på närvaron eller frånvaron av get- och set-åtkomstmetoder, klassificeras en egenskap som följer:
- En egenskap som innehåller både en get-accessor och en set-accessor sägs vara en läs- och skrivbar egenskap.
- En egenskap som har enbart en get-accessor kallas för en skrivskyddad egenskap. Det är ett kompileringsfel att en skrivskyddad egenskap är målet för en tilldelning.
- En egenskap som endast har en set-accessor kallas för en skrivskyddad egenskap. Förutom när det gäller målet för en tilldelning, är det ett kompileringsfel att referera till en skrivskyddad egenskap i ett uttryck.
Obs! Pre- och postfix
++
och--
operatorer och sammansatta tilldelningsoperatorer kan inte tillämpas på skrivbara egenskaper, eftersom dessa operatorer läser det gamla värdet för sina operander innan de skriver det nya. slutkommentar
Exempel: I följande kod
public class Button : Control { private string caption; public string Caption { get => caption; set { if (caption != value) { caption = value; Repaint(); } } } public override void Paint(Graphics g, Rectangle r) { // Painting code goes here } }
kontrollen
Button
deklarerar en offentligCaption
egendom. Get-åtkomstmetoden för Caption-egenskapen returnerarstring
som lagras i det privatacaption
-fältet. Set-åtkomstmetoden kontrollerar om det nya värdet skiljer sig från det nuvarande värdet, och om så är fallet, lagrar det nya värdet och målar om kontrollen. Egenskaper följer ofta mönstret som visas ovan: Get-accessorn returnerar helt enkelt ett värde som lagras i ettprivate
fält, och den angivna åtkomstgivaren ändrar fältetprivate
och utför sedan eventuella ytterligare åtgärder som krävs för att uppdatera objektets tillstånd fullt ut. Med tanke påButton
klassen ovan är följande ett exempel på användning avCaption
egenskapen:Button okButton = new Button(); okButton.Caption = "OK"; // Invokes set accessor string s = okButton.Caption; // Invokes get accessor
Här anropas den inställda åtkomstorn genom att tilldela ett värde till egenskapen, och get-accessorn anropas genom att referera till egenskapen i ett uttryck.
slutexempel
Get- och set-åtkomstmetoder för en egenskap är inte distinkta medlemmar, och det är inte möjligt att deklarera åtkomstmetoderna för en egenskap separat.
Exempel: Exemplet
class A { private string name; // Error, duplicate member name public string Name { get => name; } // Error, duplicate member name public string Name { set => name = value; } }
deklarerar inte en enda läs- och skrivbar egenskap. I stället deklareras två egenskaper med samma namn, en skrivskyddad och en skrivbar endast. Eftersom två medlemmar som deklareras i samma klass inte kan ha samma namn orsakar exemplet ett kompileringsfel.
slutexempel
När en härledd klass deklarerar en egenskap med samma namn som en ärvd egenskap döljer den härledda egenskapen den ärvda egenskapen med avseende på både läsning och skrivning.
Exempel: I följande kod
class A { public int P { set {...} } } class B : A { public new int P { get {...} } }
egenskapen
P
iB
döljer egenskapenP
iA
avseende på både läsning och skrivning. Således, i satsernaB b = new B(); b.P = 1; // Error, B.P is read-only ((A)b).P = 1; // Ok, reference to A.P
tilldelningen av
b.P
gör att ett kompileringsfel rapporteras, eftersom den skrivskyddadeP
-egenskapen iB
döljer den skrivbaraP
-egenskapen iA
. Observera dock att en omvandling kan användas för att komma åt den doldaP
egenskapen.slutexempel
Till skillnad från offentliga fält ger egenskaperna en separation mellan ett objekts interna tillstånd och dess offentliga gränssnitt.
Exempel: Tänk på följande kod, som använder en
Point
struct för att representera en plats:class Label { private int x, y; private string caption; public Label(int x, int y, string caption) { this.x = x; this.y = y; this.caption = caption; } public int X => x; public int Y => y; public Point Location => new Point(x, y); public string Caption => caption; }
Label
Här använder klassen tvåint
fält ochx
y
, för att lagra platsen. Platsen exponeras offentligt både som enX
- ochY
-egenskap och som enLocation
-egenskap av typenPoint
. Om det i en framtida version avLabel
blir enklare att lagra platsen som enPoint
internt kan ändringen göras utan att det offentliga gränssnittet för klassen påverkas:class Label { private Point location; private string caption; public Label(int x, int y, string caption) { this.location = new Point(x, y); this.caption = caption; } public int X => location.X; public int Y => location.Y; public Point Location => location; public string Caption => caption; }
Om
x
ochy
i stället varitpublic readonly
fält hade det varit omöjligt att göra en sådan ändring iLabel
klassen.slutexempel
Obs! Att exponera tillstånd via egenskaper är inte nödvändigtvis mindre effektivt än att exponera fält direkt. När en egenskap inte är virtuell och endast innehåller en liten mängd kod kan körningsmiljön ersätta anrop till accessorer med den faktiska koden för accessorerna. Den här processen kallas inlining och gör egenskapsåtkomsten lika effektiv som fältåtkomst, men bevarar ändå den ökade flexibiliteten för egenskaper. slutkommentar
Exempel: Eftersom det begreppsmässigt är motsvarande att läsa värdet av ett fält när man anropar en get-accessor, anses det vara dålig programmeringsstil att get-acccessorer har observerbara bieffekter. I exemplet
class Counter { private int next; public int Next => next++; }
värdet för
Next
egenskapen beror på hur många gånger egenskapen har använts tidigare. Därför ger åtkomst till egenskapen en observerbar bieffekt, och egenskapen bör implementeras som en metod i stället.Konventionen "inga biverkningar" för get-accessorer innebär inte att get-accessorer bör skrivas endast för att returnera värden som lagras i fält. Get-användare beräknar ofta värdet för en egenskap genom att komma åt flera fält eller anropa metoder. Men en korrekt utformad get-accessor utför inga åtgärder som orsakar observerbara ändringar i objektets tillstånd.
slutexempel
Egenskaper kan användas för att fördröja initieringen av en resurs tills den först refereras.
Exempel:
public class Console { private static TextReader reader; private static TextWriter writer; private static TextWriter error; public static TextReader In { get { if (reader == null) { reader = new StreamReader(Console.OpenStandardInput()); } return reader; } } public static TextWriter Out { get { if (writer == null) { writer = new StreamWriter(Console.OpenStandardOutput()); } return writer; } } public static TextWriter Error { get { if (error == null) { error = new StreamWriter(Console.OpenStandardError()); } return error; } } ... }
Klassen
Console
innehåller tre egenskaper,In
,Out
ochError
, som representerar standardenheterna för indata, utdata och fel. Genom att exponera dessa medlemmar som egenskaperConsole
kan klassen fördröja initieringen tills de faktiskt används. När du till exempel först refererar tillOut
-egenskapen, som iConsole.Out.WriteLine("hello, world");
den underliggande
TextWriter
för utgångsenheten skapas. Men om programmet inte refererar tillIn
egenskaperna ochError
skapas inga objekt för dessa enheter.slutexempel
15.7.4 Automatiskt implementerade egenskaper
En automatiskt implementerad egenskap (eller automatisk egenskap för kort) är en icke-abstrakt, icke-extern, icke-referensvärde egenskap med enbart semikolon i accessor_body. Autoegenskaper ska ha en get-accessor och kan eventuellt ha en set-accessor.
När en egenskap anges som en automatiskt implementerad egenskap är ett dolt bakgrundsfält automatiskt tillgängligt för egenskapen och åtkomsterna implementeras för att läsa från och skriva till det bakgrundsfältet. Det dolda bakgrundsfältet är otillgängligt, det kan bara läsas och skrivas via de automatiskt implementerade egenskapsåtkomsterna, även inom den innehållande typen. Om den automatiska egenskapen inte har någon set-tillgång, betraktas bakgrundsfältet som readonly
(§15.5.3). Precis som ett readonly
fält kan en skrivskyddad automatiskt egenskap också tilldelas inuti kroppen av en konstruktör i den omslutande klassen. En sådan tilldelning tilldelar direkt till fältet för skrivskyddad stöd av egenskapen.
En automatisk egenskap kan valfritt ha en property_initializer, vilket tillämpas direkt på bakåtkompatibla fältet som en variable_initializer (§17.7).
Exempel:
public class Point { public int X { get; set; } // Automatically implemented public int Y { get; set; } // Automatically implemented }
motsvarar följande deklaration:
public class Point { private int x; private int y; public int X { get { return x; } set { x = value; } } public int Y { get { return y; } set { y = value; } } }
slutexempel
Exempel: I följande
public class ReadOnlyPoint { public int X { get; } public int Y { get; } public ReadOnlyPoint(int x, int y) { X = x; Y = y; } }
motsvarar följande deklaration:
public class ReadOnlyPoint { private readonly int __x; private readonly int __y; public int X { get { return __x; } } public int Y { get { return __y; } } public ReadOnlyPoint(int x, int y) { __x = x; __y = y; } }
Tilldelningarna till det skrivskyddade fältet är giltiga eftersom de sker inom konstruktorn.
slutexempel
Även om bakgrundsfältet är dolt kan fältet ha fältriktade attribut som tillämpas direkt på det via den automatiskt implementerade egenskapens property_declaration (§15.7.1).
Exempel: Följande kod
[Serializable] public class Foo { [field: NonSerialized] public string MySecret { get; set; } }
resulterar i det fältriktade attribut
NonSerialized
som tillämpas på det kompilatorgenererade bakgrundsfältet, som om koden hade skrivits på följande sätt:[Serializable] public class Foo { [NonSerialized] private string _mySecretBackingField; public string MySecret { get { return _mySecretBackingField; } set { _mySecretBackingField = value; } } }
slutexempel
15.7.5 Hjälpmedel
Om en accessor har en accessor_modifier bestäms accessorns tillgänglighetsdomän (§7.5.3) med hjälp av den deklarerade tillgängligheten för accessor_modifier. Om en accessor inte har någon accessor_modifier bestäms tillgänglighetsdomänen för accessorn från den deklarerade tillgängligheten för egenskapen eller indexeraren.
Förekomsten av en accessor_modifier påverkar aldrig medlemsuppslag (§12.5) eller överlagringslösning (§12.6.4). Modifierarna på egenskapen eller indexeraren avgör alltid vilken egenskap eller indexerare som är bunden till, oavsett kontexten för åtkomsten.
När en viss icke-referensvärdesegenskap eller icke-referensvärdesindexerare har valts används tillgänglighetsdomänerna för de specifika berörda åtkomsterna för att avgöra om användningen är giltig:
- Om användningen är som ett värde (§12.2.2), ska get-accessorn finnas och vara tillgänglig.
- Om användningen är målet för en enkel tilldelning (§12.21.2), ska den angivna accessorn finnas och vara tillgänglig.
- Om användningen är som målet för sammansatt tilldelning (§12.21.4), eller som målet för
++
- eller--
-operatörerna (§12.8.16, §12.9.6), ska både get-accessorerna och set-accessorn finnas och vara tillgängliga.
Exempel: I följande exempel döljs egenskapen
A.Text
av egenskapenB.Text
, även i kontexter där endast den angivna åtkomstorn anropas. EgenskapenB.Count
är däremot inte tillgänglig för klassenM
, så den tillgängliga egenskapenA.Count
används i stället.class A { public string Text { get => "hello"; set { } } public int Count { get => 5; set { } } } class B : A { private string text = "goodbye"; private int count = 0; public new string Text { get => text; protected set => text = value; } protected new int Count { get => count; set => count = value; } } class M { static void Main() { B b = new B(); b.Count = 12; // Calls A.Count set accessor int i = b.Count; // Calls A.Count get accessor b.Text = "howdy"; // Error, B.Text set accessor not accessible string s = b.Text; // Calls B.Text get accessor } }
slutexempel
När en viss referensvärdesegenskap eller referensvärdesindexerare har valts – om användningen är som ett värde, målet för en enkel tilldelning eller målet för en sammansatt tilldelning – används tillgänglighetsdomänen för den berörda get-accessorn för att avgöra om användningen är giltig.
En accessor som används för att implementera ett gränssnitt får inte ha någon accessor_modifier. Om endast en accessor används för att implementera ett gränssnitt kan den andra accessorn deklareras med en accessor_modifier:
Exempel:
public interface I { string Prop { get; } } public class C : I { public string Prop { get => "April"; // Must not have a modifier here internal set {...} // Ok, because I.Prop has no set accessor } }
slutexempel
15.7.6 Virtuella, förseglade, åsidosatta och abstrakta accessorer
Obs! Den här satsen gäller både egenskaper (§15.7) och indexerare (§15.9). Satsen är skriven i termer av egenskaper. Vid läsning för indexerare, ersätt indexerare/indexerare med egenskap/egenskaper och konsultera listan över skillnader mellan egenskaper och indexerare som finns i §15.9.2. slutkommentar
En virtuell egenskapsdeklaration anger att åtkomstmetoderna för egenskapen är virtuella.
virtual
-modifieraren tillämpas på alla icke-privata åtkomstmetoder hos en egenskap. När en accessor för en virtuell egenskap har private
accessor_modifier, är åtkomsten till den privata egenskapen implicit inte virtuell.
En abstrakt egenskapsdeklaration anger att accessorerna för egenskapen är virtuella, men tillhandahåller inte någon faktisk implementering av dessa. I stället krävs det att icke-abstrakta härledda klasser tillhandahåller sin egen implementering för accessorerna genom att åsidosätta egenskapen. Eftersom en accessor för en abstrakt egenskapsdeklaration inte tillhandahåller någon faktisk implementering består dess accessor_body helt enkelt av ett semikolon. En abstrakt egenskap får inte ha någon private
accessor.
En egenskapsdeklaration som innehåller både abstract
modifierare och override
anger att egenskapen är abstrakt och åsidosätter en basegenskap. Åtkomstmetoderna för en sådan egenskap är också abstrakta.
Abstrakta egenskapsdeklarationer tillåts endast i abstrakta klasser (§15.2.2.2). Åtkomstmetoderna för en ärvd virtuell egenskap kan åsidosättas i en efterföljande klass genom att inkludera en egenskapsdeklaration som specificerar en override
direktiv. Detta kallas för en åsidosättande egenskapsdeklaration. En åsidosättande egenskapsdeklaration deklarerar inte en ny egenskap. Istället specialiserar den helt enkelt implementeringarna av åtkomstmetoderna för en befintlig virtuell egenskap.
Åsidosättningsdeklarationen och den åsidosatta basegenskapen måste ha samma deklarerade åtkomstnivå. Med andra ord, en åsidosättningsdeklaration får inte ändra åtkomsten av baskomponenten. Men om den åsidosatta basegenskapen är skyddad internt och den deklareras i en annan sammansättning än den sammansättning som innehåller åsidosättningsdeklarationen ska åsidosättningsdeklarationens deklarerade tillgänglighet skyddas. Om den ärvda egenskapen bara har en enda accessor (dvs. om den ärvda egenskapen är skrivskyddad eller skrivbaserad) ska den åsidosättande egenskapen endast innehålla den accessorn. Om den ärvda egenskapen innehåller båda accessorerna (dvs. om den ärvda egenskapen är både läsbar och skrivbar) kan den överordnade egenskapen innehålla antingen en enskild accessor eller båda accessorerna. Det ska ske en identitetskonvertering mellan den överskridande och den ärvda egenskapens typ.
En överskrivande egenskapsdeklaration kan innehålla modifieraren sealed
. Användning av den här modifieraren förhindrar att en härledd klass ytterligare åsidosättar egenskapen. Åtkomstmetoderna för en förseglad egenskap är också förseglade.
Förutom skillnader i deklarations- och anropssyntax fungerar virtuella, förseglade, åsidosättnings- och abstrakta accessorer exakt som virtuella, förseglade, åsidosättnings- och abstrakta metoder. Närmare bestämt gäller de regler som beskrivs i §15.6.4, §15.6.5, §15.6.6 och §15.6.7 som om accessorer vore metoder av motsvarande form:
- En get-accessor motsvarar en parameterlös metod med ett returvärde av egenskapstypen och samma modifierare som den innehållande egenskapen.
- En set-accessor motsvarar en metod med en enda värdeparameter av egenskapens typ, en void-returtyp och samma modifierare som den omslutande egenskapen.
Exempel: I följande kod
abstract class A { int y; public virtual int X { get => 0; } public virtual int Y { get => y; set => y = value; } public abstract int Z { get; set; } }
X
är en virtuell skrivskyddad egenskap,Y
är en virtuell läs- och skrivbar egenskap, ochZ
är en abstrakt läs- och skrivbar egenskap. EftersomZ
är abstrakt ska den innehållande klassen A också deklareras abstrakt.En klass som härleds från
A
visas nedan:class B : A { int z; public override int X { get => base.X + 1; } public override int Y { set => base.Y = value < 0 ? 0: value; } public override int Z { get => z; set => z = value; } }
Här är deklarationerna för
X
,Y
ochZ
överordnande egenskapsdeklarationer. Varje egenskapsdeklaration överensstämmer exakt med åtkomstmodifierarna, typen och namnet på motsvarande ärvda egenskap. Get-accessorn avX
och set-accessorn avY
använder basnyckelordet för att få åtkomst till de ärvda åtkomstgivarna. Deklarationen avZ
åsidosätter båda abstrakta accessorer – därför finns det inga uteståendeabstract
funktionsmedlemmar iB
ochB
tillåts vara en icke-abstrakt klass.slutexempel
När en egenskap deklareras som en override, ska alla åsidosatta accessorer vara tillgängliga för den åsidosättande koden. Dessutom ska den deklarerade tillgängligheten för både själva egenskapen eller indexeraren, och för åtkomstgivarna, överensstämma med den åsidosatta medlemmens och åtkomstgivarnas.
Exempel:
public class B { public virtual int P { get {...} protected set {...} } } public class D: B { public override int P { get {...} // Must not have a modifier here protected set {...} // Must specify protected here } }
slutexempel
15.8 Händelser
15.8.1 Allmänt
En händelse är en medlem som gör det möjligt för ett objekt eller en klass att tillhandahålla meddelanden. Klienter kan koppla körbar kod för händelser genom att tillhandahålla händelsehanterare.
Händelser deklareras med event_declaration s:
event_declaration
: attributes? event_modifier* 'event' type variable_declarators ';'
| attributes? event_modifier* 'event' type member_name
'{' event_accessor_declarations '}'
;
event_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| 'readonly' // direct struct members only
| unsafe_modifier // unsafe code support
;
event_accessor_declarations
: add_accessor_declaration remove_accessor_declaration
| remove_accessor_declaration add_accessor_declaration
;
add_accessor_declaration
: attributes? 'add' block
;
remove_accessor_declaration
: attributes? 'remove' block
;
unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).
En event_declaration kan innehålla en uppsättning attribut (§22) och någon av de tillåtna typerna av deklarerad tillgänglighet (§15.3.6), new
(§15.3.5), static
(§15.6.3, §15.8.4), virtual
(§15.6.4, §15.8.5), override
(§15.6.5, §15.8.5), sealed
(§15.6.6), abstract
(§15.6.7, §15.8.5) och extern
(§15.6.8) modifierare. Dessutom kan en event_declaration som finns direkt av en struct_declaration inkludera readonly
modifieraren (§16.4.12).
Händelsedeklarationer omfattas av samma regler som metoddeklarationer (§15.6) när det gäller giltiga kombinationer av modifierare.
Typ av händelsedeklaration ska vara en delegate_type (§8.2.8), och den delegate_type ska vara minst lika tillgänglig som själva händelsen (§7.5.5).
En händelsedeklaration kan innehålla event_accessor_declarations. Men om den inte gör det, för icke-externa, icke-abstrakta händelser, ska en kompilator tillhandahålla dem automatiskt (§15.8.2); för extern
händelser tillhandahålls åtkomsterna externt.
En händelsedeklaration som utelämnar event_accessor_declarations definierar en eller flera händelser – en för var och en av de variable_declarator. Attributen och modifierarna gäller för alla medlemmar som deklareras av en sådan event_declaration.
Det är ett kompileringsfel för en event_declaration att inkludera både abstract
modifieraren och event_accessor_declarations.
När en händelsedeklaration innehåller en extern
modifierare sägs händelsen vara en extern händelse. Eftersom en extern händelsedeklaration inte innehåller någon faktisk implementering, är det ett misstag att inkludera både extern
-modifieraren och event_accessor_declarations.
Det är ett kompileringstidsfel för en variabeldeklarator i en händelsedeklaration med en abstract
eller external
modifierare att inkludera en variabelinitierare.
En händelse kan användas som den vänstra operanden för operatorerna +=
och -=
. Dessa operatorer används för att koppla händelsehanterare till eller för att ta bort händelsehanterare från en händelse, och åtkomstmodifierarna för händelsen kontrollerar de kontexter där sådana åtgärder tillåts.
De enda åtgärder som tillåts för en händelse med kod som ligger utanför den typ där händelsen deklareras är +=
och -=
. Även om sådan kod kan lägga till och ta bort hanterare för en händelse kan den därför inte hämta eller ändra den underliggande listan över händelsehanterare direkt.
Vid en operation av formen x += y
eller x –= y
har resultatet typen x
när void
är en händelse (§12.21.5), till skillnad från att ha typen av x
med värdet x
efter tilldelningen, vilket är fallet för andra +=
och -=
operatorer som definieras på icke-händelsetyper. Detta förhindrar att extern kod indirekt undersöker den underliggande delegaten för en händelse.
Exempel: I följande exempel visas hur händelsehanterare är kopplade till instanser av
Button
klassen:public delegate void EventHandler(object sender, EventArgs e); public class Button : Control { public event EventHandler Click; } public class LoginDialog : Form { Button okButton; Button cancelButton; public LoginDialog() { okButton = new Button(...); okButton.Click += new EventHandler(OkButtonClick); cancelButton = new Button(...); cancelButton.Click += new EventHandler(CancelButtonClick); } void OkButtonClick(object sender, EventArgs e) { // Handle okButton.Click event } void CancelButtonClick(object sender, EventArgs e) { // Handle cancelButton.Click event } }
LoginDialog
Här skapar instanskonstruktorn tvåButton
instanser och kopplar händelsehanterare tillClick
händelserna.slutexempel
15.8.2 Fältliknande händelser
I programtexten för klassen eller structen som innehåller deklarationen av en händelse kan vissa händelser användas som fält. För att användas på detta sätt ska en händelse inte vara abstrakt eller extern och ska inte uttryckligen innehålla event_accessor_declarations. En sådan händelse kan användas i alla sammanhang som tillåter ett fält. Fältet innehåller ett ombud (§20), som refererar till listan över händelsehanterare som har lagts till i händelsen. Om inga händelsehanterare har lagts till innehåller null
fältet .
Exempel: I följande kod
public delegate void EventHandler(object sender, EventArgs e); public class Button : Control { public event EventHandler Click; protected void OnClick(EventArgs e) { EventHandler handler = Click; if (handler != null) { handler(this, e); } } public void Reset() => Click = null; }
Click
används som ett fält iButton
klassen. Som exemplet visar kan fältet undersökas, ändras och användas i delegerade anropsuttryck. MetodenOnClick
iButton
klassen "höjer"Click
händelsen. Tanken på att ta upp en händelse är exakt likvärdig med att anropa ombudet som representeras av händelsen – därför finns det inga särskilda språkkonstruktioner för att höja händelser. Observera att delegeringsanropet föregås av en kontroll som säkerställer att delegeringen inte är null och att kontrollen görs på en lokal kopia för att säkerställa trådsäkerhet.Utanför klassens
Button
Click
deklaration kan medlemmen endast användas till vänster om operatorerna+=
och–=
som ib.Click += new EventHandler(...);
som lägger till ett ombud i anropslistan för
Click
händelsen, ochClick –= new EventHandler(...);
som tar bort ett ombud från anropslistan för
Click
händelsen.slutexempel
Vid kompilering av en fältliknande händelse ska en kompilator automatiskt skapa lagring för ombudet och skapa åtkomst till händelsen som lägger till eller tar bort händelsehanterare i ombudsfältet. Tilläggs- och borttagningsåtgärderna är trådsäkra och kan (men krävs inte) utföras när låset (§13.13) hålls kvar på det innehållande objektet för en instanshändelse eller System.Type
objektet (§12.8.18) för en statisk händelse.
Obs! En instanshändelsedeklaration i formen:
class X { public event D Ev; }
ska sammanställas till någonting som motsvarar
class X { private D __Ev; // field to hold the delegate public event D Ev { add { /* Add the delegate in a thread safe way */ } remove { /* Remove the delegate in a thread safe way */ } } }
I klassen
X
leder referenser tillEv
på den vänstra sidan av+=
- och–=
-operatorerna till att tilläggs- och borttagningsaccessorerna anropas. Alla andra referenser tillEv
kompileras för att referera till det dolda fältet__Ev
i stället (§12.8.7). Namnet "__Ev
" är godtyckligt. Det dolda fältet kan ha valfritt namn eller inget namn alls.slutkommentar
15.8.3 Event-åtkomstmetoder
Obs! Händelsedeklarationer utelämnar vanligtvis event_accessor_declarations, som i exemplet
Button
ovan. De kan till exempel inkluderas om lagringskostnaden för ett fält per händelse inte är acceptabel. I sådana fall kan en klass inkludera event_accessor_declarations och använda en privat mekanism för att lagra listan över händelsehanterare. slutkommentar
I event_accessor_declarations för en händelse anger du de körbara instruktioner som är associerade med att lägga till och ta bort händelsehanterare.
Åtkomstdeklarationerna består av en lägg_till_åtkomstdeklaration och en ta_bort_åtkomstdeklaration. Varje åtkomstdeklaration består av antingen lägga till- eller ta bort-token följt av ett block. Blocket som är associerat med en add_accessor_declaration anger de instruktioner som ska köras när en händelsehanterare läggs till, och blocket som är associerat med en remove_accessor_declaration anger de instruktioner som ska köras när en händelsehanterare tas bort.
Varje add_accessor_declaration och remove_accessor_declaration motsvarar en metod med en enskild värdeparameter av händelsetypen och en void
returtyp. Den implicita parametern för en händelseåtkomstor heter value
. När en händelse används i en händelsehantering används lämplig åtkomstmetod. Mer specifikt, om tilldelningsoperatorn är +=
används lägg till-accessorn, och om tilldelningsoperatorn är –=
används ta bort-accessorn. I båda fallen används högeroperanden för tilldelningsoperatorn som argument till händelse-åtkomstmetoden. Blocket för en add_accessor_declaration eller en remove_accessor_declaration skall överensstämma med de regler för void
metoder som beskrivs i §15.6.9. I synnerhet får satser i return
i ett sådant block inte ange ett uttryck.
Eftersom en händelseåtkomst implicit har en parameter med namnet value
, är det ett kompileringsfel för en lokal variabel eller konstant som deklarerats i en händelseåtkomst att ha det namnet.
Exempel: I följande kod
class Control : Component { // Unique keys for events static readonly object mouseDownEventKey = new object(); static readonly object mouseUpEventKey = new object(); // Return event handler associated with key protected Delegate GetEventHandler(object key) {...} // Add event handler associated with key protected void AddEventHandler(object key, Delegate handler) {...} // Remove event handler associated with key protected void RemoveEventHandler(object key, Delegate handler) {...} // MouseDown event public event MouseEventHandler MouseDown { add { AddEventHandler(mouseDownEventKey, value); } remove { RemoveEventHandler(mouseDownEventKey, value); } } // MouseUp event public event MouseEventHandler MouseUp { add { AddEventHandler(mouseUpEventKey, value); } remove { RemoveEventHandler(mouseUpEventKey, value); } } // Invoke the MouseUp event protected void OnMouseUp(MouseEventArgs args) { MouseEventHandler handler; handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey); if (handler != null) { handler(this, args); } } }
Control
-klassen implementerar en intern lagringsmekanism för händelser. MetodenAddEventHandler
associerar ett ombudsvärde med en nyckel,GetEventHandler
metoden returnerar det ombud som för närvarande är associerat med en nyckel ochRemoveEventHandler
metoden tar bort ett ombud som händelsehanterare för den angivna händelsen. Förmodligen är den underliggande lagringsmekanismen utformad så att det inte kostar något att associera ett null-delegatvärde med en nyckel, och därför förbrukar ohanterade händelser inget lagringsutrymme.slutexempel
15.8.4 Statiska händelser och instanshändelser
När en händelsedeklaration innehåller en static
modifierare sägs händelsen vara en statisk händelse. När ingen static
modifierare finns, sägs händelsen vara en instanshändelse.
En statisk händelse är inte associerad med en specifik instans, och det är ett kompileringsfel att referera till this
för en statisk händelses åtkomstfunktioner.
En händelseinstans är associerad med en given instans av en klass, och denna instans kan nås som this
(§12.8.14) i händelsens accessorer.
Skillnaderna mellan statiska medlemmar och instansmedlemmar diskuteras ytterligare i §15.3.8.
15.8.5 Virtuella, förseglade, åsidosättnings- och abstrakta accessorer
En virtuell händelsedeklaration anger att åtkomsten till den händelsen är virtuell. Modifieraren virtual
gäller för båda åtkomstpunkterna för en händelse.
En abstrakt händelsedeklaration anger att åtkomsten till händelsen är virtuell, men ger ingen faktisk implementering av åtkomsterna. I stället måste icke-abstrakta härledda klasser tillhandahålla sin egen implementering för accessorerna genom att åsidosätta händelsen. Eftersom en accessor för en abstrakt händelsedeklaration inte tillhandahåller något faktiskt genomförande ska den inte tillhandahålla event_accessor_declarations.
En händelsedeklaration som innehåller både abstract
modifierarna och override
anger att händelsen är abstrakt och åsidosätter en bashändelse. Accessorerna för en sådan händelse är också abstrakta.
Abstrakta händelsedeklarationer tillåts endast i abstrakta klasser (§15.2.2.2).
Åtkomst till en ärvd virtuell händelse kan åsidosättas i en härledd klass genom att inkludera en händelsedeklaration som anger override
-modifieraren. Detta kallas för en övergripande händelsedeklaration. En åsidosättande händelsedeklaration deklarerar inte en ny händelse. I stället specialiserar den sig helt enkelt på implementeringarna av accessorerna för en befintlig virtuell händelse.
En åsidosättande händelsedeklaration ska ange exakt samma åtkomstmodifierare och namn som den åsidosatta händelsen, det ska finnas en identitetsomvandling mellan typen av den åsidosättande och den åsidosatta händelsen, och både tilläggs- och borttagningsaccessorerna ska anges i deklarationen.
En övergripande händelsedeklaration kan innehålla sealed
modifieraren. Användning av this
modifierare hindrar en härledd klass från att åsidosätta händelsen ytterligare. Åtkomstmetoderna för en förseglad händelse är också förseglade.
Det är ett kompileringsfel för en övergripande händelsedeklaration som ska innehålla en new
modifierare.
Förutom skillnader i deklarations- och anropssyntax fungerar virtuella, förseglade, åsidosättnings- och abstrakta accessorer exakt som virtuella, förseglade, åsidosättnings- och abstrakta metoder. Närmare bestämt gäller de regler som beskrivs i §15.6.4, §15.6.5, §15.6.6 och §15.6.7 som om accessorer vore metoder av motsvarande form. Varje accessor motsvarar en metod med en enskild värdeparameter av händelsetypen, en void
returtyp och samma modifierare som den innehållande händelsen.
15,9 indexerare
15.9.1 Allmänt
En indexerare är en medlem som gör att ett objekt kan indexeras på samma sätt som en matris. Indexerare deklareras med indexer_declaration s:
indexer_declaration
: attributes? indexer_modifier* indexer_declarator indexer_body
| attributes? indexer_modifier* ref_kind indexer_declarator ref_indexer_body
;
indexer_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| 'readonly' // direct struct members only
| unsafe_modifier // unsafe code support
;
indexer_declarator
: type 'this' '[' parameter_list ']'
| type interface_type '.' 'this' '[' parameter_list ']'
;
indexer_body
: '{' accessor_declarations '}'
| '=>' expression ';'
;
ref_indexer_body
: '{' ref_get_accessor_declaration '}'
| '=>' 'ref' variable_reference ';'
;
unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).
En indexer_declaration kan innehålla en uppsättning attribut (§22) och någon av de tillåtna typerna av deklarerad tillgänglighet (§15.3.6), new
(§15.3.5), virtual
(§15.15.1 6.4), override
(§15.6.5), sealed
(§15.6.6), abstract
(§15.6.7) och extern
(§15.6.8) modifierare. Dessutom kan en indexer_declaration som finns direkt av en struct_declaration inkludera readonly
modifieraren (§16.4.12).
- Den första deklarerar en icke-referensvärdesindexerare. Dess värde har typen type. Den här typen av indexerare kan vara läsbar och/eller skrivbar.
- Den andra deklarerar en referensvärdesindexerare. Dess värde är en variabelreferens (§9.5), som kan vara
readonly
, till en variabel av typen typ. Den här typen av indexerare är bara läsbar.
En indexer_declaration kan innehålla en uppsättning attribut (§22) och någon av de tillåtna typerna av deklarerad tillgänglighet (§15.3.6), new
(§15.3.5), virtual
(§15.15.) 6.4), override
(§15.6.5), sealed
(§15.6.6), abstract
(§15.6.7) och extern
(§15.6.8) modifierare.
Indexerarens deklarationer omfattas av samma regler som metoddeklarationer (§15.6) när det gäller giltiga kombinationer av modifierare, med det enda undantaget att modifieraren inte är tillåten static
i en indexerardeklaration.
Typen av indexeraredeklaration anger elementtypen för indexeraren som introducerades av deklarationen.
Obs! Eftersom indexerare är utformade för att användas i matriselementliknande kontexter används även termelementtypensom definierats för en matris med en indexerare. slutkommentar
Om inte indexeraren är en explicit implementering av en gränssnittsmedlem, följs typen av nyckelordet this
. För en explicit implementation av ett gränssnittselement följs typen av ett gränssnittstyp, ett ".
" och nyckelordet this
. Till skillnad från andra medlemmar har indexerare inte användardefinierade namn.
Parameter_list anger indexerarens parametrar. Parameterlistan för en indexerare motsvarar en metod (§15.6.2), förutom att minst en parameter ska anges och att this
modifierarna , ref
och out
parametern inte är tillåtna.
Indexerarens typ och var och en av de typer som anges i parameter_list ska vara minst lika tillgänglig som indexeraren själv (§7.5.5).
En indexer_body kan antingen bestå av en instruktionstext (§15.7.1) eller en uttryckstext (§15.6.1). I en uttalandekropp, accessor_declarations, som ska omges av "{
" och "}
" token, deklarerar åtkomsterna hos indexeraren (§15.7.3). Åtkomstmetoderna anger de körbara instruktioner som är associerade med att läsa och skriva indexerarelement.
I en indexer_body en uttryckstext som består av "=>
" följt av ett uttryck E
och ett semikolon är exakt likvärdigt med uttryckstexten { get { return E; } }
och kan därför endast användas för att ange skrivskyddade indexerare där resultatet av get-accessorn ges av ett enda uttryck.
En ref_indexer_body kan antingen bestå av en satskropp eller en uttryckskropp. I ett satsblock deklarerar get_accessor_declaration get-accessorn (§15.7.3) för indexeraren. Accessorn anger de körbara instruktioner som är associerade med att läsa indexeraren.
I en ref_indexer_body är en expressionskropp bestående av =>
följt av ref
, en variable_referenceV
och ett semikolon exakt motsvarigheten till uttryckskroppen { get { return ref V; } }
.
Obs! Även om syntaxen för att komma åt ett indexerarelement är densamma som för ett matriselement klassificeras inte ett indexerarelement som en variabel. Därför är det inte möjligt att passera ett indexerarelement som ett
in
,out
ellerref
argument om inte indexeraren är referensvärde och därför returnerar en referens (§9.7). slutkommentar
Indexerarens parameter_list definierar indexerarens signatur (§7.6). Mer specifikt består signaturen för en indexerare av antalet och typerna av dess parametrar. Elementtypen och namnen på parametrarna ingår inte i en indexerares signatur.
En indexerares signatur ska skilja sig från signaturerna för alla andra indexerare som deklarerats i samma klass.
När en indexeraredeklaration innehåller en extern
modifierare sägs indexeraren vara en extern indexerare. Eftersom en extern indexerardeklaration inte ger någon faktisk implementering ska var och en av de accessor_bodyi sin accessor_declarations vara semikolon.
Exempel: Exemplet nedan deklarerar en
BitArray
klass som implementerar en indexerare för åtkomst till enskilda bitar i bitmatrisen.class BitArray { int[] bits; int length; public BitArray(int length) { if (length < 0) { throw new ArgumentException(); } bits = new int[((length - 1) >> 5) + 1]; this.length = length; } public int Length => length; public bool this[int index] { get { if (index < 0 || index >= length) { throw new IndexOutOfRangeException(); } return (bits[index >> 5] & 1 << index) != 0; } set { if (index < 0 || index >= length) { throw new IndexOutOfRangeException(); } if (value) { bits[index >> 5] |= 1 << index; } else { bits[index >> 5] &= ~(1 << index); } } } }
En instans av
BitArray
klassen förbrukar betydligt mindre minne än en motsvarandebool[]
(eftersom varje värde för den förra endast upptar en bit i stället för den senares enbyte
), men tillåter samma åtgärder som enbool[]
.Följande
CountPrimes
klass använder enBitArray
och den klassiska "sieve-algoritmen" för att beräkna antalet primtal mellan 2 och ett givet maximum:class CountPrimes { static int Count(int max) { BitArray flags = new BitArray(max + 1); int count = 0; for (int i = 2; i <= max; i++) { if (!flags[i]) { for (int j = i * 2; j <= max; j += i) { flags[j] = true; } count++; } } return count; } static void Main(string[] args) { int max = int.Parse(args[0]); int count = Count(max); Console.WriteLine($"Found {count} primes between 2 and {max}"); } }
Observera att syntaxen för att komma åt element i
BitArray
är exakt samma som för enbool[]
.I följande exempel visas en 26×10-rutnätsklass som har en indexerare med två parametrar. Första parametern måste vara en bokstav från A till Z, och den andra parametern måste vara ett heltal från 0 till 9.
class Grid { const int NumRows = 26; const int NumCols = 10; int[,] cells = new int[NumRows, NumCols]; public int this[char row, int col] { get { row = Char.ToUpper(row); if (row < 'A' || row > 'Z') { throw new ArgumentOutOfRangeException("row"); } if (col < 0 || col >= NumCols) { throw new ArgumentOutOfRangeException ("col"); } return cells[row - 'A', col]; } set { row = Char.ToUpper(row); if (row < 'A' || row > 'Z') { throw new ArgumentOutOfRangeException ("row"); } if (col < 0 || col >= NumCols) { throw new ArgumentOutOfRangeException ("col"); } cells[row - 'A', col] = value; } } }
slutexempel
15.9.2 Indexerare och egenskapsskillnader
Indexerare och egenskaper är mycket lika begrepp, men skiljer sig åt på följande sätt:
- En egenskap identifieras med dess namn, medan en indexerare identifieras med dess signatur.
- En egenskap nås via en simple_name (§12.8.4) eller en member_access (§12.8.7), medan ett indexerarelement nås via en element_access (§12.8.12.3).
- En egenskap kan vara en statisk medlem, medan en indexerare alltid är instansmedlem.
- En get-accessor för en egenskap motsvarar en metod utan parametrar, medan en get-accessor för en indexerare motsvarar en metod med samma parameterlista som indexeraren.
- En uppsättningsåtkomst för en egenskap motsvarar en metod med en enda parameter med namnet
value
, medan en uppsättningsåtkomstor för en indexerare motsvarar en metod med samma parameterlista som indexeraren, plus en ytterligare parameter med namnetvalue
. - Det är ett kompileringsfel för en indexerarmetod att deklarera en lokal variabel eller lokal konstant med samma namn som en parameter för indexeraren.
- I en överskrivande egenskapsdeklaration åtkommer man den ärvda egenskapen med syntaxen
base.P
, därP
är egenskapsnamnet. I en övergripande indexeringsdeklaration används den ärvda indexeraren med hjälp av syntaxenbase[E]
, därE
är en kommaavgränsad lista med uttryck. - Det finns inget begrepp om en "automatiskt implementerad indexerare". Det är ett fel att ha en icke-abstrakt, icke-extern indexerare med semikolon accessor_bodys.
Förutom dessa skillnader gäller alla regler som definieras i §15.7.3, §15.7.5 och §15.7.6 för såväl indexerare som för fastighetsåtkomster.
Denna ersättning av egenskap/egenskaper med indexerare vid läsning av §15.7.3, §15.7.5 och §15.7.6 gäller även för definierade termer. Specifikt blir läs- och skriv egenskapläs- och skriv indexerare, endast läs egenskap blir endast läs indexerare, och endast skriv egenskap blir endast skriv indexerare.
15.10 Operatörer
15.10.1 Allmänt
En operator är en medlem som definierar innebörden av en uttrycksoperator som kan tillämpas på instanser av klassen. Operatorer deklareras med operator_declaration s:
operator_declaration
: attributes? operator_modifier+ operator_declarator operator_body
;
operator_modifier
: 'public'
| 'static'
| 'extern'
| unsafe_modifier // unsafe code support
;
operator_declarator
: unary_operator_declarator
| binary_operator_declarator
| conversion_operator_declarator
;
unary_operator_declarator
: type 'operator' overloadable_unary_operator '(' fixed_parameter ')'
;
logical_negation_operator
: '!'
;
overloadable_unary_operator
: '+' | '-' | logical_negation_operator | '~' | '++' | '--' | 'true' | 'false'
;
binary_operator_declarator
: type 'operator' overloadable_binary_operator
'(' fixed_parameter ',' fixed_parameter ')'
;
overloadable_binary_operator
: '+' | '-' | '*' | '/' | '%' | '&' | '|' | '^' | '<<'
| right_shift | '==' | '!=' | '>' | '<' | '>=' | '<='
;
conversion_operator_declarator
: 'implicit' 'operator' type '(' fixed_parameter ')'
| 'explicit' 'operator' type '(' fixed_parameter ')'
;
operator_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).
Obs! Prefixets logiska negation (§12.9.4) och postfixets null-förlåtande operatorer (§12.8.9), medan de representeras av samma lexikala token (!
), är distinkta. Den senare är inte en överladdningsbar operator.
slutkommentar
Det finns tre kategorier av överbelastningsbara operatorer: Unary-operatorer (§15.10.2), binära operatorer (§15.10.3) och konverteringsoperatorer (§15.10.4).
Operator_body är antingen ett semikolon, en blockkropp (§15.6.1) eller en uttryckstext (§15.6.1). En blocktext består av ett block som anger vilka instruktioner som ska köras när operatorn anropas. Blocket ska överensstämma med reglerna för värdereturmetoder som beskrivs i §15.6.11. En uttryckstext består av =>
följt av ett uttryck och ett semikolon och anger ett enda uttryck som ska utföras när operatorn anropas.
För extern
operatorer består operator_body helt enkelt av ett semikolon. För alla andra operatorer är operator_body antingen en blocktext eller en uttryckstext.
Följande regler gäller för alla operatordeklarationer:
- En operatörsdeklaration ska innehålla både en
public
och enstatic
modifierare. - Operatorns parametrar får inga andra modifierare än
in
. - En operatörs underskrift (§15.10.2, §15.10.3, §15.10.4) skall skilja sig från signaturerna för alla andra aktörer som deklarerats i samma klass.
- Alla typer som anges i en operatörsdeklaration ska vara minst lika tillgängliga som operatören själv (§7.5.5).
- Det är ett fel att samma modifierare visas flera gånger i en operatordeklaration.
Varje operatorkategori inför ytterligare begränsningar, enligt beskrivningen i följande underklasuler.
Precis som andra medlemmar ärvs operatorer som deklareras i en basklass av härledda klasser. Eftersom operatörsdeklarationer alltid kräver klassen eller structen där operatorn deklareras delta i operatorns signatur, är det inte möjligt för en operator som deklarerats i en härledd klass att dölja en operator som deklarerats i en basklass. Modifieraren new
krävs alltså aldrig, och tillåts därför aldrig, i en operatörsdeklaration.
Ytterligare information om unary och binära operatorer finns i §12.4.
Ytterligare information om konverteringsoperatörer finns i §10.5.
15.10.2 Unary operator
Följande regler gäller för unary-operatordeklarationer, där T
anger instanstypen för klassen eller structen som innehåller operatordeklarationen:
- En unary
+
,-
,!
(endast logisk negation) eller~
operator ska ta en enda parameter av typenT
ellerT?
och kan returnera vilken typ som helst. - En unary
++
eller--
operator ska ta en enskild parameter av typenT
ellerT?
och ska returnera samma typ eller en typ som härleds från den. - En unary
true
ellerfalse
operator ska ta en enskild parameter av typenT
ellerT?
och ska returnera typbool
.
Signaturen för en unary-operator består av operatortoken (+
, , -
!
, ~
, ++
, --
, true
eller false
) och typen av den enskilda parametern. Returtypen är inte en del av en unary-operatorns signatur, och inte heller namnet på parametern.
Operatorerna true
och false
unary kräver parvis deklaration. Ett kompileringsfel uppstår om en klass deklarerar en av dessa operatorer utan att även deklarera den andra. Operatörerna true
och false
beskrivs vidare i §12.24.
Exempel: I följande exempel visas en implementering och efterföljande användning av operator++ för en heltalsvektorklass:
public class IntVector { public IntVector(int length) {...} public int Length { get { ... } } // Read-only property public int this[int index] { get { ... } set { ... } } // Read-write indexer public static IntVector operator++(IntVector iv) { IntVector temp = new IntVector(iv.Length); for (int i = 0; i < iv.Length; i++) { temp[i] = iv[i] + 1; } return temp; } } class Test { static void Main() { IntVector iv1 = new IntVector(4); // Vector of 4 x 0 IntVector iv2; iv2 = iv1++; // iv2 contains 4 x 0, iv1 contains 4 x 1 iv2 = ++iv1; // iv2 contains 4 x 2, iv1 contains 4 x 2 } }
Observera hur operatormetoden returnerar värdet som genereras genom att lägga till 1 i operanden, precis som postfixets inkrements- och decrementoperatorer (§12.8.16) och prefixets inkrements- och decrementoperatorer (§12.9.6). Till skillnad från i C++ bör den här metoden inte ändra värdet för sin operand direkt eftersom detta skulle strida mot standardsemantiken för postfixstegsoperatorn. (§12.8.16)
slutexempel
15.10.3 Binära operatorer
Följande regler gäller för deklarationer av binära operatorer, där T
anger instanstypen för klassen eller structen som innehåller operatordeklarationen:
- En binär icke-skiftoperator ska ha två parametrar, varav minst en ska ha typ
T
ellerT?
, och kan returnera vilken typ som helst. - En binär
<<
eller>>
operator (§12.11) ska ha två parametrar, varav den första ska ha typT
ellerT?
och den andra av vilka ska ha typint
ellerint?
, och kan returnera vilken typ som helst.
Signaturen för en binär operator består av operatortoken (+
, , -
*
, /
, %
, &
, |
^
, , <<
, >>
, ==
, !=
>
, <
, >=
, eller <=
) och typerna av de två parametrarna. Returtypen och namnen på parametrarna ingår inte i en binär operators signatur.
Vissa binära operatorer kräver parvis deklaration. För varje deklaration av en operatör av ett par ska det finnas en matchande deklaration av den andra operatören i paret. Två operatordeklarationer överensstämmer om det finns identitetskonverteringar mellan deras returtyper och motsvarande parametertyper. Följande operatorer kräver parvis deklaration:
- operator
==
och operator!=
- operator
>
och operator<
- operator
>=
och operator<=
15.10.4 Konverteringsoperatorer
En konverteringsoperatordeklaration introducerar en användardefinierad konvertering (§10.5), som utökar de fördefinierade implicita och explicita konverteringarna.
En konverteringsoperatordeklaration som innehåller nyckelordet implicit
introducerar en användardefinierad implicit konvertering. Implicita omvandlingar kan inträffa i en mängd olika situationer, inklusive funktionsmedlemsanrop, typomvandlingar och tilldelningar. Detta beskrivs ytterligare i §10.2.
En konverteringsoperatordeklaration som innehåller nyckelordet explicit
introducerar en användardefinierad explicit konvertering. Explicita konverteringar kan ske i gjutna uttryck och beskrivs ytterligare i §10.3.
En konverteringsoperator konverterar från en källtyp som anges av parametertypen för konverteringsoperatorn till en måltyp som anges av konverteringsoperatorns returtyp.
För en viss källtyp S
och måltyp T
, om S
eller T
är nullbara värdetyper, låt S₀
och T₀
referera till deras underliggande typer; annars, är S₀
och T₀
lika med S
respektive T
. En klass eller struct tillåts deklarera en konvertering från en källtyp S
till en måltyp T
endast om allt av följande är sant:
S₀
ochT₀
är olika typer.Antingen
S₀
ellerT₀
är instanstypen för klassen eller structen som innehåller operatordeklarationen.Varken
S₀
ellerT₀
är en interface_type.Förutom användardefinierade konverteringar finns det ingen konvertering från
S
tillT
eller frånT
tillS
.
I dessa regler anses alla typparametrar som är associerade med S
eller T
anses vara unika typer som inte har någon arvsrelation med andra typer, och eventuella begränsningar för dessa typparametrar ignoreras.
Exempel: I följande:
class C<T> {...} class D<T> : C<T> { public static implicit operator C<int>(D<T> value) {...} // Ok public static implicit operator C<string>(D<T> value) {...} // Ok public static implicit operator C<T>(D<T> value) {...} // Error }
de två första operatordeklarationerna tillåts eftersom
T
respektiveint
string
betraktas som unika typer utan relation. Den tredje operatorn är dock ett fel eftersomC<T>
är basklassen förD<T>
.slutexempel
Av den andra regeln följer att en konverteringsoperatör ska konvertera antingen till eller från den klass eller den structtyp där operatorn deklareras.
Exempel: Det är möjligt för en klass- eller structtyp
C
att definiera en konvertering frånC
tillint
och frånint
tillC
, men inte frånint
tillbool
. slutexempel
Det går inte att omdefiniera en fördefinierad konvertering direkt. Konverteringsoperatorer kan därför inte konvertera från eller till object
eftersom implicita och explicita konverteringar redan finns mellan object
och alla andra typer. På samma sätt kan varken källan eller måltyperna för en konvertering vara en bastyp för den andra, eftersom en konvertering då redan skulle finnas. Det är dock möjligt att deklarera operatorer för generiska typer som för vissa typargument anger konverteringar som redan finns som fördefinierade konverteringar.
Exempel:
struct Convertible<T> { public static implicit operator Convertible<T>(T value) {...} public static explicit operator T(Convertible<T> value) {...} }
när typen
object
anges som ett typargument förT
deklarerar den andra operatorn en konvertering som redan finns (en implicit och därför också en explicit konvertering finns från valfri typ till typobjekt).slutexempel
Om det finns en fördefinierad konvertering mellan två typer ignoreras alla användardefinierade konverteringar mellan dessa typer. Specifikt:
- Om det finns en fördefinierad implicit konvertering (§10.2) från typ
S
till typT
ignoreras alla användardefinierade konverteringar (implicita eller explicita) frånS
tillT
. - Om det finns en fördefinierad explicit konvertering (§10.3) från typ
S
till typT
ignoreras alla användardefinierade explicita konverteringar frånS
tillT
. Dessutom:- Om antingen
S
ellerT
är en gränssnittstyp ignoreras användardefinierade implicita konverteringar frånS
tillT
. - I annat fall beaktas fortfarande användardefinierade implicita konverteringar från
S
tillT
.
- Om antingen
För alla typer men object
är operatorerna som deklareras av Convertible<T>
typen ovan inte i konflikt med fördefinierade konverteringar.
Exempel:
void F(int i, Convertible<int> n) { i = n; // Error i = (int)n; // User-defined explicit conversion n = i; // User-defined implicit conversion n = (Convertible<int>)i; // User-defined implicit conversion }
För typ
object
döljer fördefinierade konverteringar dock de användardefinierade konverteringarna i alla fall utom en:void F(object o, Convertible<object> n) { o = n; // Pre-defined boxing conversion o = (object)n; // Pre-defined boxing conversion n = o; // User-defined implicit conversion n = (Convertible<object>)o; // Pre-defined unboxing conversion }
slutexempel
Användardefinierade konverteringar tillåts inte att konvertera från eller till interface_types. I synnerhet säkerställer den här begränsningen att inga användardefinierade transformeringar sker vid konvertering till en interface_type, och att en konvertering till en interface_type endast lyckas om konverteringen object
faktiskt implementerar den angivna interface_type.
Signaturen för en konverteringsoperator består av källtypen och måltypen. (Detta är den enda medlemsform som returtypen deltar i signaturen för.) Den implicita eller explicita klassificeringen av en konverteringsoperator ingår inte i operatorns signatur. Därför kan en klass eller struct inte deklarera både en implicit och en explicit konverteringsoperator med samma käll- och måltyper.
Obs! I allmänhet bör användardefinierade implicita konverteringar utformas för att aldrig utlösa undantag och aldrig förlora information. Om en användardefinierad konvertering kan ge upphov till undantag (till exempel på grund av att källargumentet är utom räckhåll) eller förlust av information (till exempel att ignorera bitar med hög ordning) bör konverteringen definieras som en explicit konvertering. slutkommentar
Exempel: I följande kod
public struct Digit { byte value; public Digit(byte value) { if (value < 0 || value > 9) { throw new ArgumentException(); } this.value = value; } public static implicit operator byte(Digit d) => d.value; public static explicit operator Digit(byte b) => new Digit(b); }
konverteringen från
Digit
tillbyte
är implicit eftersom den aldrig utlöser undantag eller förlorar information, men konverteringen frånbyte
tillDigit
är explicit eftersomDigit
den bara kan representera en delmängd av möjliga värden för enbyte
.slutexempel
15.11 Instanskonstruktorer
15.11.1 Allmänt
En instanskonstruktor är medlem som implementerar de åtgärder som krävs för att initiera en instans av en klass. Instanskonstruktorer deklareras med constructor_declaration s:
constructor_declaration
: attributes? constructor_modifier* constructor_declarator constructor_body
;
constructor_modifier
: 'public'
| 'protected'
| 'internal'
| 'private'
| 'extern'
| unsafe_modifier // unsafe code support
;
constructor_declarator
: identifier '(' parameter_list? ')' constructor_initializer?
;
constructor_initializer
: ':' 'base' '(' argument_list? ')'
| ':' 'this' '(' argument_list? ')'
;
constructor_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).
En constructor_declaration kan innehålla en uppsättning attribut (§22), någon av de tillåtna typerna av deklarerad tillgänglighet (§15.3.6) och en extern
(§15.6.8) modifierare. En konstruktordeklaration får inte innehålla samma modifierare flera gånger.
Identifieraren för en constructor_declarator ska namnge den klass där instanskonstruktorn deklareras. Om något annat namn anges uppstår ett kompileringsfel.
Den valfria parameter_list för en instanskonstruktor omfattas av samma regler som parameter_list för en metod (§15.6).
this
Eftersom modifieraren för parametrar endast gäller för tilläggsmetoder (§15.6.10) ska ingen parameter i en konstruktors parameter_list innehålla this
modifieraren. Parameterlistan definierar signaturen (§7.6) för en instanskonstruktor och styr processen där överbelastningsmatchning (§12.6.4) väljer en viss instanskonstruktor i ett anrop.
Var och en av de typer som anges i parameter_list av en instanskonstruktor ska vara minst lika tillgänglig som konstruktorn själv (§7.5.5).
Den valfria constructor_initializer anger en annan instanskonstruktor som ska anropas innan de instruktioner som anges i constructor_body för den här instanskonstruktorn körs. Detta beskrivs ytterligare i §15.11.2.
När en konstruktordeklaration innehåller en extern
modifierare sägs konstruktorn vara en extern konstruktor. Eftersom en extern konstruktordeklaration inte tillhandahåller någon verklig implementering består dess constructor_body av ett semikolon. För alla andra konstruktorer består constructor_body av antingen
- ett block som anger instruktioner för att initiera en ny instans av klassen, eller
- en uttryckstext, som består av
=>
följt av ett uttryck och ett semikolon, och anger ett enda uttryck för att initiera en ny instans av klassen.
En constructor_body som är en block- eller uttrycksdel exakt motsvarar blocket för en instansmetod med en void
returtyp (§15.6.11).
Instanskonstruktörer ärver inte. Därför har en klass inga andra instanskonstruktorer än de som faktiskt deklareras i klassen, med undantag för att om en klass inte innehåller några instanskonstruktordeklarationer tillhandahålls automatiskt en standardinstanskonstruktor (§15.11.5).
Instanskonstruktorer anropas av object_creation_expressions (§12.8.17.2) och genom constructor_initializers.
15.11.2 Konstruktorinitierare
Alla instanskonstruktorer (förutom för klass object
) inkluderar implicit en anrop av en annan instanskonstruktor omedelbart före constructor_body. Konstruktorn som implicit anropas bestäms av constructor_initializer:
- En instanskonstruktorinitierare av formuläret
base(
argument_list)
(där argument_list är valfritt) gör att en instanskonstruktor från den direkta basklassen anropas. Konstruktorn väljs med hjälp av argument_list och reglerna för överbelastningsmatchning i §12.6.4. Uppsättningen med kandidatinstanskonstruktorer består av alla tillgängliga instanskonstruktorer för den direkta basklassen. Om den här uppsättningen är tom, eller om en enda bästa instanskonstruktor inte kan identifieras, uppstår ett kompileringsfel. - En instanskonstruktorinitierare av formuläret
this(
argument_list)
(där argument_list är valfritt) anropar en annan instanskonstruktor från samma klass. Konstruktorn väljs med hjälp av argument_list och reglerna för överbelastningsmatchning av §12.6.4. Uppsättningen med kandidatinstanskonstruktorer består av alla instanskonstruktorer som deklarerats i själva klassen. Om den resulterande uppsättningen av tillämpliga instanskonstruktorer är tom, eller om en enda bästa instanskonstruktor inte kan identifieras, uppstår ett kompileringsfel. Om en instanskonstruktordeklaration anropar sig själv via en kedja med en eller flera konstruktorinitierare uppstår ett kompileringsfel.
Om en instanskonstruktor inte har någon konstruktorinitierare tillhandahålls implicit en konstruktorinitierare av formuläret base()
.
Obs! En instanskonstruktordeklaration av formuläret
C(...) {...}
är exakt likvärdigt med
C(...) : base() {...}
slutkommentar
Omfånget för de parametrar som anges av parameter_list för en instanskonstruktordeklaration innehåller konstruktorinitieraren för den deklarationen. Därför tillåts en konstruktorinitierare att komma åt konstruktorns parametrar.
Exempel:
class A { public A(int x, int y) {} } class B: A { public B(int x, int y) : base(x + y, x - y) {} }
slutexempel
En instanskonstruktorinitierare kan inte komma åt den instans som skapas. Därför är det ett kompileringsfel att referera till detta i ett argumentuttryck för konstruktorns initialiserare, eftersom det är ett kompileringsfel för ett argumentuttryck för att referera till alla instansmedlemmar via en simple_name.
15.11.3 Instansvariabelinitierare
När en icke-extern instanskonstruktor inte har någon konstruktorinitierare, eller om den har en konstruktorinitierare av formuläret base(...)
, utför konstruktorn implicit de initieringar som anges av variable_initializeri instansfälten som deklareras i dess klass. Detta motsvarar en sekvens av tilldelningsoperationer som körs omedelbart vid inträdet i konstruktorn och före det implicita anropet av den direkta basklasskonstruktorn. Variabelinitierarna körs i textordningen där de visas i klassdeklarationen (§15.5.6).
Variabelinitierare behöver inte köras av externa instanskonstruktorer.
15.11.4 Konstruktörskörning
Variabelinitierare omvandlas till tilldelningsinstruktioner och dessa tilldelningsinstruktioner körs före anropet av basklassinstanskonstruktorn. Den här ordningen säkerställer att alla instansfält initieras av deras variabelinitierare innan några instruktioner som har åtkomst till den instansen körs.
Exempel: Givet följande:
class A { public A() { PrintFields(); } public virtual void PrintFields() {} } class B: A { int x = 1; int y; public B() { y = -1; } public override void PrintFields() => Console.WriteLine($"x = {x}, y = {y}"); }
när nytt
B()
används för att skapa en instans avB
skapas följande utdata:x = 1, y = 0
Värdet
x
för är 1 eftersom variabelinitieraren körs innan basklassinstanskonstruktorn anropas. Värdety
för är dock 0 (standardvärdet för enint
) eftersom tilldelningen tilly
inte körs förrän efter att basklasskonstruktorn har returnerat. Det är användbart att tänka på instansvariabelinitierare och konstruktorinitierare som instruktioner som infogas automatiskt före constructor_body. Exempletclass A { int x = 1, y = -1, count; public A() { count = 0; } public A(int n) { count = n; } } class B : A { double sqrt2 = Math.Sqrt(2.0); ArrayList items = new ArrayList(100); int max; public B(): this(100) { items.Add("default"); } public B(int n) : base(n - 1) { max = n; } }
innehåller flera variabelinitierare. den innehåller också konstruktorinitierare av båda formerna (
base
ochthis
). Exemplet motsvarar den kod som visas nedan, där varje kommentar anger en automatiskt infogad instruktion (syntaxen som används för automatiskt infogade konstruktoranrop är inte giltig, utan bara används för att illustrera mekanismen).class A { int x, y, count; public A() { x = 1; // Variable initializer y = -1; // Variable initializer object(); // Invoke object() constructor count = 0; } public A(int n) { x = 1; // Variable initializer y = -1; // Variable initializer object(); // Invoke object() constructor count = n; } } class B : A { double sqrt2; ArrayList items; int max; public B() : this(100) { B(100); // Invoke B(int) constructor items.Add("default"); } public B(int n) : base(n - 1) { sqrt2 = Math.Sqrt(2.0); // Variable initializer items = new ArrayList(100); // Variable initializer A(n - 1); // Invoke A(int) constructor max = n; } }
slutexempel
15.11.5 Standardkonstruktörer
Om en klass inte innehåller några instanskonstruktordeklarationer tillhandahålls automatiskt en standardinstanskonstruktor. Den standardkonstruktorn anropar helt enkelt en konstruktor för den direkta basklassen, som om den hade en konstruktorinitierare av formuläret base()
. Om klassen är abstrakt skyddas den deklarerade tillgängligheten för standardkonstruktorn. Annars är den deklarerade tillgängligheten för standardkonstruktorn offentlig.
Obs! Därför är standardkonstruktorn alltid i formen av
protected C(): base() {}
eller
public C(): base() {}
där
C
är namnet på klassen.slutkommentar
Om överbelastningsupplösningen inte kan fastställa en unik bästa kandidat för basklasskonstruktorns initialiserare uppstår ett kompileringsfel.
Exempel: I följande kod
class Message { object sender; string text; }
en standardkonstruktor tillhandahålls eftersom klassen inte innehåller några instanskonstruktordeklarationer. Exemplet är alltså exakt detsamma som
class Message { object sender; string text; public Message() : base() {} }
slutexempel
15.12 Statiska konstruktorer
En statisk konstruktor är en medlem som implementerar de åtgärder som krävs för att initiera en stängd klass. Statiska konstruktorer deklareras med static_constructor_declaration s:
static_constructor_declaration
: attributes? static_constructor_modifiers identifier '(' ')'
static_constructor_body
;
static_constructor_modifiers
: 'static'
| 'static' 'extern' unsafe_modifier?
| 'static' unsafe_modifier 'extern'?
| 'extern' 'static' unsafe_modifier?
| 'extern' unsafe_modifier 'static'
| unsafe_modifier 'static' 'extern'?
| unsafe_modifier 'extern' 'static'
;
static_constructor_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).
En static_constructor_declaration kan innehålla en uppsättning attribut (§22) och en extern
modifierare (§15.6.8).
Identifieraren för en static_constructor_declaration ska namnge den klass där den statiska konstruktorn deklareras. Om något annat namn anges uppstår ett kompileringsfel.
När en statisk konstruktordeklaration innehåller en extern
modifierare sägs den statiska konstruktorn vara en extern statisk konstruktor. Eftersom en extern statisk konstruktordeklaration inte tillhandahåller någon faktisk implementering består dess static_constructor_body av ett semikolon. För alla andra statiska konstruktordeklarationer består static_constructor_body av antingen
- ett block som anger vilka instruktioner som ska köras för att initiera klassen, eller
- en uttryckstext, som består av
=>
följt av ett uttryck och ett semikolon, och anger ett enda uttryck som ska köras för att initiera klassen.
En static_constructor_body som är ett block eller uttrycksblock motsvarar exakt metodkroppen hos en statisk metod med en void
enligt §15.6.11.
Statiska konstruktorer ärvs inte och kan inte anropas direkt.
Den statiska konstruktorn för en sluten klass körs högst en gång i en viss programdomän. Körningen av en statisk konstruktor utlöses av den första av följande händelser som inträffar inom en programdomän:
- En instans av klassen skapas.
- Alla statiska medlemmar i klassen refereras till.
Om en klass innehåller metoden Main
(§7.1) där körningen börjar körs den statiska konstruktorn för den Main
klassen innan metoden anropas.
För att initiera en ny typ av sluten klass skapas först en ny uppsättning statiska fält (§15.5.2) för den specifika stängda typen. Vart och ett av de statiska fälten ska initieras till standardvärdet (§15.5.5). Så här gör du:
- Om det antingen inte finns någon statisk konstruktor eller en icke-extern statisk konstruktor:
- De statiska fältinitierarna (§15.5.6.2) ska utföras för dessa statiska fält.
- Sedan ska den statiska konstruktorn, om någon, som inte är extern, utföras.
- Annars ska den utföras om det finns en extern statisk konstruktor. Statiska variabelinitierare krävs inte för att köras av externa statiska konstruktorer.
Exempel: Exemplet
class Test { static void Main() { A.F(); B.F(); } } class A { static A() { Console.WriteLine("Init A"); } public static void F() { Console.WriteLine("A.F"); } } class B { static B() { Console.WriteLine("Init B"); } public static void F() { Console.WriteLine("B.F"); } }
måste generera utdata:
Init A A.F Init B B.F
eftersom körningen av
A
den statiska konstruktorn utlöses av anropet tillA.F
och körningen avB
den statiska konstruktorn utlöses av anropet tillB.F
.slutexempel
Det är möjligt att konstruera cirkulära beroenden som gör att statiska fält med variabelinitierare kan observeras i deras standardvärdetillstånd.
Exempel: Exemplet
class A { public static int X; static A() { X = B.Y + 1; } } class B { public static int Y = A.X + 1; static B() {} static void Main() { Console.WriteLine($"X = {A.X}, Y = {B.Y}"); } }
genererar utdata
X = 1, Y = 2
För att köra
Main
metoden kör systemet först initiatorn förB.Y
, före klassensB
statiska konstruktor.Y
s initierare gör att konstruktornA
förstatic
körs eftersom värdetA.X
refereras till. Den statiska konstruktornA
i sin tur fortsätter på att beräkna värdet avX
, och hämtar därmed standardvärdetY
, som är noll.A.X
initieras därför till 1. Processen med att köraA
initiatorer för statiska fält och statisk konstruktor slutförs och återgår till beräkningen av det initiala värdet förY
, vars resultat blir 2.slutexempel
Eftersom den statiska konstruktorn körs exakt en gång för varje sluten konstruerad klasstyp är det en lämplig plats att tillämpa körningskontroller på typparametern som inte kan kontrolleras vid kompileringstid via begränsningar (§15.2.5).
Exempel: Följande typ använder en statisk konstruktor för att framtvinga att typargumentet är en uppräkning:
class Gen<T> where T : struct { static Gen() { if (!typeof(T).IsEnum) { throw new ArgumentException("T must be an enum"); } } }
slutexempel
15.13 Finaliserare
Obs! I en tidigare version av den här specifikationen kallades det som nu kallas "finalizer" för en "destructor". Erfarenheten har visat att termen "destructor" orsakade förvirring och ofta resulterade i felaktiga förväntningar, särskilt för programmerare som känner till C++. I C++ anropas en destruktor på ett bestämt sätt, medan en finalizer i C# inte gör det. För att få avgörande beteende från C#, bör man använda
Dispose
. slutkommentar
En finalizer är en medlem som implementerar de åtgärder som krävs för att slutföra en instans av en klass. En finaliserare deklareras med hjälp av en finalizer_declaration:
finalizer_declaration
: attributes? '~' identifier '(' ')' finalizer_body
| attributes? 'extern' unsafe_modifier? '~' identifier '(' ')'
finalizer_body
| attributes? unsafe_modifier 'extern'? '~' identifier '(' ')'
finalizer_body
;
finalizer_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).
En finalizer_declaration kan innehålla en uppsättning attribut (§22).
Identifieraren för en finalizer_declarator ska namnge den klass där slutdatorn deklareras. Om något annat namn anges uppstår ett kompileringsfel.
När en slutklareringsdeklaration innehåller en extern
modifierare sägs finalatorn vara en extern finalator. Eftersom en extern slutklareringsdeklaration inte innehåller någon faktisk implementering består dess finalizer_body av ett semikolon. För alla andra finalizers består finalizer_body av antingen
- ett block som anger vilka instruktioner som ska köras för att slutföra en instans av klassen.
- eller en uttryckstext, som består av
=>
följt av ett uttryck och ett semikolon, och anger ett enda uttryck som ska köras för att slutföra en instans av klassen.
En finalizer_body som är ett block eller uttrycksblock motsvarar exakt method_body av en instansmetod med en void
returtyp (§15.6.11).
Finalizers ärvs inte. En klass har alltså inga finaliseringsmetoder förutom den som kan deklareras i den klassen.
Obs! Eftersom en finalator krävs för att inte ha några parametrar kan den inte överbelastas, så en klass kan ha högst en finalator. slutkommentar
Finalisatorer anropas automatiskt och kan inte anropas uttryckligen. En instans blir berättigad till slutförande när det inte längre är möjligt för någon kod att använda den instansen. Körning av finalizern för instansen kan ske när som helst efter att instansen har blivit berättigad till slutförande (§7.9). När en instans avslutas, anropas finalisatorerna i instansens arvkedja i ordning, från den mest härledda till den minst härledda. En finalizer kan köras på valfri tråd. Mer information om de regler som styr när och hur en finalator körs finns i §7.9.
Exempel: Utdata från exemplet
class A { ~A() { Console.WriteLine("A's finalizer"); } } class B : A { ~B() { Console.WriteLine("B's finalizer"); } } class Test { static void Main() { B b = new B(); b = null; GC.Collect(); GC.WaitForPendingFinalizers(); } }
är
B's finalizer A's finalizer
sedan finalizers i en arvkedja anropas i ordning, från mest härledd till minst härledd.
slutexempel
Finalizers implementeras genom att ersätta den virtuella metoden Finalize
på System.Object
. C#-program får inte åsidosätta den här metoden eller anropa den (eller åsidosättningar av den) direkt.
Exempel: Till exempel programmet
class A { override protected void Finalize() {} // Error public void F() { this.Finalize(); // Error } }
innehåller två fel.
slutexempel
En kompilator ska bete sig som om den här metoden och åsidosättningar av den inte finns alls.
Exempel: Det här programmet:
class A { void Finalize() {} // Permitted }
är giltig och metoden som visas döljer
System.Object
sFinalize
metod.slutexempel
För en diskussion om beteendet när ett undantag utlöses från en finalator, se §21.4.
15.14 Asynkrona Funktioner
15.14.1 Allmänt
En metod (§15.6) eller anonym funktion (§12.19) med async
modifieraren kallas för en async function. I allmänhet används termen asynkron för att beskriva alla typer av funktioner som har async
modifieraren.
Det är ett kompileringsfel om parameterlistan i en asynkron funktion specificerar någon in
, out
, eller ref
-parameter, eller någon parameter av ref struct
-typen.
Return_type för en asynkron metod ska vara antingen void
, en aktivitetstyp eller en asynkron iteratortyp (§15.15). För en asynkron metod som genererar ett resultatvärde ska en aktivitetstyp eller en asynkron iteratortyp (§15.15.3) vara generisk. För en asynkron metod som inte ger ett resultatvärde ska en aktivitetstyp inte vara generisk. Sådana typer anges i den här specifikationen som «TaskType»<T>
respektive «TaskType»
. Standardbibliotekstypen System.Threading.Tasks.Task
och typerna som konstruerats från System.Threading.Tasks.Task<TResult>
och System.Threading.Tasks.ValueTask<T>
är aktivitetstyper, samt en typ av klass, struct eller gränssnitt som är associerad med en aktivitetsbyggaretyp via attributet System.Runtime.CompilerServices.AsyncMethodBuilderAttribute
. Sådana typer anges i den här specifikationen som «TaskBuilderType»<T>
och «TaskBuilderType»
. En aktivitetstyp kan ha högst en typparameter och kan inte kapslas i en allmän typ.
En asynkron metod som returnerar en task-typ sägs vara task-returning.
Aktivitetstyper kan variera i sin exakta definition, men från språkets synvinkel är en aktivitetstyp i något av tillstånden ofullständig, lyckades eller felade. En felaktig uppgift registrerar ett relevant undantag.
Lyckad«TaskType»<T>
registrerar ett resultat av typen T
. Task-typer är väntbara, och uppgifter kan därför vara operander för väntuttryck (§12.9.8).
Exempel: Aktivitetstypen
MyTask<T>
är associerad med aktivitetsbyggarens typMyTaskMethodBuilder<T>
och inväntartypenAwaiter<T>
:using System.Runtime.CompilerServices; [AsyncMethodBuilder(typeof(MyTaskMethodBuilder<>))] class MyTask<T> { public Awaiter<T> GetAwaiter() { ... } } class Awaiter<T> : INotifyCompletion { public void OnCompleted(Action completion) { ... } public bool IsCompleted { get; } public T GetResult() { ... } }
slutexempel
En aktivitetsbyggare är en typ av klass eller struct som motsvarar en specifik aktivitetstyp (§15.14.2). Uppgiftsbyggarens typ ska exakt matcha den deklarerade tillgängligheten för motsvarande aktivitetstyp.
Obs! Om aktivitetstypen deklareras
internal
måste motsvarande builder-typ också deklarerasinternal
och definieras i samma sammansättning. Om aktivitetstypen är kapslad i en annan typ måste uppgiftsbyggartypen också vara kapslad i samma typ. slutkommentar
En asynkron funktion har förmåga att avbryta utvärdering genom await-uttryck (§12.9.8) i sin kropp. Utvärderingen kan senare återupptas vid det inväntningsuttryck där den avbröts, med hjälp av ett återtagandedeligat. Återtagandedelegaten är av typen System.Action
, och när den anropas återupptas utvärderingen av den asynkrona funktionens anrop från det väntande uttrycket där det avbröts. Den aktuella uppringaren för ett asynkront funktionsanrop är den ursprungliga uppringaren om funktionsanropet aldrig har avbrutits eller annars den senaste uppringaren av återupptagningsdelegeringen.
15.14.2 Byggmönster av aktivitetstyp
En uppgiftsbyggartyp kan ha högst en typparameter och kan inte kapslas i en generisk typ. En typ av aktivitetsbyggare ska ha följande medlemmar (för icke-generiska aktivitetsbyggartyper har SetResult
inga parametrar) med deklarerad public
tillgänglighet:
class «TaskBuilderType»<T>
{
public static «TaskBuilderType»<T> Create();
public void Start<TStateMachine>(ref TStateMachine stateMachine)
where TStateMachine : IAsyncStateMachine;
public void SetStateMachine(IAsyncStateMachine stateMachine);
public void SetException(Exception exception);
public void SetResult(T result);
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine;
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine;
public «TaskType»<T> Task { get; }
}
En kompilator ska generera kod som använder "TaskBuilderType" för att implementera semantiken för att pausa och återuppta utvärderingen av asynkron funktion. En kompilator ska använda "TaskBuilderType" på följande sätt:
-
«TaskBuilderType».Create()
anropas för att skapa en instans av «TaskBuilderType», med namnetbuilder
i den här listan. -
builder.Start(ref stateMachine)
anropas för att associera byggaren med en kompilatorgenererad tillståndsdatorinstans,stateMachine
.- Byggverktyget ska anropa
stateMachine.MoveNext()
antingen iStart()
eller efter attStart()
har returnerat för att driva tillståndsmaskinen framåt.
- Byggverktyget ska anropa
- Efter att
Start()
har returnerat, anroparasync
metodenbuilder.Task
för att uppgiften ska returnera från async-metoden. - Varje anrop till
stateMachine.MoveNext()
kommer att avancera tillståndsmaskinen. - Om tillståndsmaskinen slutförs framgångsrikt anropas
builder.SetResult()
, med metodens returvärde, om det finns något. - Annars, om ett undantag kastas i tillståndsmaskinen, anropas
e
builder.SetException(e)
. - Om tillståndsdatorn når ett
await expr
uttryckexpr.GetAwaiter()
anropas. - Om väntaren implementerar
ICriticalNotifyCompletion
ochIsCompleted
är falskt, anropar tillståndsmaskinenbuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine)
.-
AwaitUnsafeOnCompleted()
ska anropaawaiter.UnsafeOnCompleted(action)
med enAction
som anroparstateMachine.MoveNext()
när väntaren är klar.
-
- Annars anropar tillståndsmaskinen
builder.AwaitOnCompleted(ref awaiter, ref stateMachine)
.-
AwaitOnCompleted()
ska anropaawaiter.OnCompleted(action)
med enAction
som anroparstateMachine.MoveNext()
när väntaren är klar.
-
-
SetStateMachine(IAsyncStateMachine)
kan anropas av den kompilatorgenereradeIAsyncStateMachine
implementeringen för att identifiera instansen av byggaren som är associerad med en tillståndsdatorinstans, särskilt när tillståndsdatorn implementeras som en värdetyp.- Om byggaren anropar
stateMachine.SetStateMachine(stateMachine)
, kommerstateMachine
anropabuilder.SetStateMachine(stateMachine)
på den byggarinstans som är associerad medstateMachine
.
- Om byggaren anropar
Notera: För både
SetResult(T result)
och«TaskType»<T> Task { get; }
måste parametern och argumentet vara identitetskonverterbara tillT
. Detta gör att en uppgiftstypbyggare kan stödja typer som tuplar, där två typer som inte är desamma kan konverteras identiskt. slutkommentar
15.14.3 Utvärdering av en aktivitetsreturerande asynkron funktion
Anrop av en aktivitetsreturerande asynkron funktion gör att en instans av den returnerade aktivitetstypen genereras. Detta kallas för returuppgiften för den asynkrona funktionen. Aktiviteten är ursprungligen i ett ofullständigt tillstånd.
Den asynkrona funktionskroppen utvärderas sedan tills den antingen pausas (genom att nå await-uttrycket) eller avslutas, då kontrollen returneras till anroparen, tillsammans med returuppgiften.
När kroppen av async-funktionen avslutas, flyttas returuppgiften från det ofullständiga tillståndet.
- Om funktionskroppen avslutas som resultat av att nå en return-sats eller slutet av kroppen, registreras eventuellt resultatvärde i returuppgiften, som sätts i ett succeeded-tillstånd.
- Om funktionskroppen avslutas på grund av en oavlyssnad
OperationCanceledException
, registreras undantaget i återgångsuppgiften som sätts i läget avbruten. - Om funktionskroppen avslutas som ett resultat av någon annan oinfångad undantag (§13.10.6), registreras undantaget i returuppgiften som försätts i ett faulted tillstånd.
15.14.4 Utvärdering av en asynkron funktion som inte returnerar något värde
Om returtypen för funktionen async är void
skiljer sig utvärderingen från ovanstående på följande sätt: Eftersom ingen uppgift returneras kommunicerar funktionen i stället slutförande och undantag till den aktuella trådens synkroniseringskontext. Den exakta definitionen av synkroniseringskontexten är implementeringsberoende, men är en representation av "var" den aktuella tråden körs. Synkroniseringskontexten meddelas när utvärderingen av en void
-returnerande asynkron funktion påbörjas, slutförs framgångsrikt eller gör att ett ohanterat undantag genereras.
På så sätt kan kontexten hålla reda på hur många void
-returnerande asynkrona funktioner som körs under den och bestämma hur undantag ska hanteras utifrån dessa.
15.15 Synkrona och asynkrona iteratorer
15.15.1 Allmänt
En funktionsmedlem (§12.6) eller lokal funktion (§13.6.4) som implementeras med hjälp av ett iteratorblock (§13.3) kallas en iterator. Ett iteratorblock kan användas som en funktionsmedlems brödtext så länge som returtypen för motsvarande funktionsmedlem är ett av uppräkningsgränssnitten (§15.15.2) eller något av de uppräkningsbara gränssnitten (§15.15.3).
En asynkron funktion (§15.14) som implementeras med hjälp av ett iteratorblock (§13.3) kallas för en asynkron iterator. Ett asynkront iteratorblock kan användas som en funktionsmedlems brödtext så länge som returtypen för motsvarande funktionsmedlem är de asynkrona uppräkningsgränssnitten (§15.15.2) eller de asynkrona uppräkningsbara gränssnitten (§15.15.3).
Ett iteratorblock kan inträffa som en method_body, operator_body eller accessor_body, medan händelser, instanskonstruktorer, statiska konstruktorer och finalator inte ska implementeras som synkrona eller asynkrona iteratorer.
När en funktionsmedlem eller lokal funktion implementeras med hjälp av ett iteratorblock är det ett kompileringsfel för parameterlistan av funktionsmedlemmen att specificera några in
, out
, eller ref
parametrar eller en parameter av en ref struct
typ.
15.15.2 Uppräkningsgränssnitt
Uppräkningsgränssnitten är det icke-generiska gränssnittet System.Collections.IEnumerator
och alla instansier av de generiska gränssnitten System.Collections.Generic.IEnumerator<T>
.
De asynkrona uppräkningsgränssnitten är alla instansier av det generiska gränssnittet System.Collections.Generic.IAsyncEnumerator<T>
.
För korthets skull refereras dessa gränssnitt i den här underklausulen och dess syskon som IEnumerator
, IEnumerator<T>
, och IAsyncEnumerator<T>
.
15.15.3 Uppräkningsbara gränssnitt
De uppräkningsbara gränssnitten är det icke-generiska gränssnittet System.Collections.IEnumerable
och alla instansieringar av de generiska gränssnitten System.Collections.Generic.IEnumerable<T>
.
De asynkrona uppräkningsbara gränssnitten är alla instansier av det generiska gränssnittet System.Collections.Generic.IAsyncEnumerable<T>
.
För korthets skull refereras dessa gränssnitt i den här underklausulen och dess syskon som IEnumerable
, IEnumerable<T>
, och IAsyncEnumerable<T>
.
15.15.4 Avkastningstyp
En iterator genererar en sekvens med värden, alla av samma typ. Den här typen kallas för iteratorns avkastningstyp.
- Avkastningstypen för en iterator som returnerar
IEnumerator
ellerIEnumerable
ärobject
. - Avkastningstypen för en iterator som returnerar en
IEnumerator<T>
,IAsyncEnumerator<T>
,IEnumerable<T>
ellerIAsyncEnumerable<T>
ärT
.
15.15.5 Uppräkningsobjekt
15.15.5.1 Allmänt
När en funktionsmedlem eller lokal funktion som returnerar en uppräkningsgränssnittstyp implementeras med hjälp av ett iteratorblock, körs inte koden i iteratorblocket direkt när funktionen anropas. I stället skapas och returneras ett uppräkningsobjekt. Det här objektet kapslar in koden som anges i iteratorblocket och körningen av koden i iteratorblocket inträffar när uppräkningsobjektets MoveNext
eller MoveNextAsync
-metoden anropas. Ett uppräkningsobjekt har följande egenskaper:
- Den implementerar
System.IDisposable
,IEnumerator
ochIEnumerator<T>
, ellerSystem.IAsyncDisposable
ochIAsyncEnumerator<T>
, därT
är iteratorns avkastningstyp. - Den initieras med en kopia av argumentvärdena (om några) och instansvärdet skickas till funktionsmedlemmen.
- Det har fyra potentiella tillstånd, före, körs, pausas och efter, och är ursprungligen i tillståndet före .
Ett uppräkningsobjekt är vanligtvis en instans av en kompilatorgenererad uppräkningsklass som kapslar in koden i iteratorblocket och implementerar uppräkningsgränssnitten, men andra implementeringsmetoder är möjliga. Om en uppräkningsklass genereras av kompilatorn, kapslas den klassen, direkt eller indirekt, i klassen som innehåller funktionsmedlemmen, den har privat tillgänglighet och har ett namn som är reserverat för kompilatoranvändning (§6.4.3).
Ett uppräkningsobjekt kan implementera fler gränssnitt än de som anges ovan.
Följande underfunktioner beskriver medlemmens beteende som krävs för att föra uppräknaren framåt, hämta det aktuella värdet från uppräknaren och ta bort resurser som används av uppräknaren. Dessa definieras i följande medlemmar för synkrona respektive asynkrona uppräknare:
- Så här avancerar du uppräknaren:
MoveNext
ochMoveNextAsync
. - Så här hämtar du det aktuella värdet:
Current
. - Så här gör du av med resurser:
Dispose
ochDisposeAsync
.
Uppräkningsobjekt stöder IEnumerator.Reset
inte metoden. Om du anropar den här metoden kastas ett System.NotSupportedException
.
Synkrona och asynkrona iteratorblock skiljer sig åt eftersom asynkrona iteratormedlemmar returnerar uppgiftstyper och kan vänta.
15.15.5.2 Föra uppräknaren framåt
Metoderna MoveNext
och MoveNextAsync
för ett uppräkningsobjekt kapslar in koden för ett iteratorblock. Genom att anropa metoden MoveNext
eller MoveNextAsync
körs koden i iteratorblocket, och Current
-egenskapen hos uppräkningsobjektet ställs in efter behov.
MoveNext
returnerar ett bool
värde vars innebörd beskrivs nedan.
MoveNextAsync
returnerar en ValueTask<bool>
(§15.14.3). Resultatvärdet för aktiviteten som returneras från MoveNextAsync
har samma betydelse som resultatvärdet från MoveNext
. I följande beskrivning gäller MoveNext
de åtgärder som beskrivs för MoveNextAsync
med följande skillnad: Om det anges att MoveNext
returnerar true
eller false
anger MoveNextAsync
dess uppgift till slutfört tillstånd och anger aktivitetens resultatvärde till motsvarande true
värde eller false
värde.
Den exakta åtgärd som utförs av MoveNext
eller MoveNextAsync
beror på tillståndet för uppräkningsobjektet när det anropas:
- Om uppräkningsobjektets tillstånd är tidigare anropar du
MoveNext
:- Ändrar tillståndet till körande.
- Initierar parametrarna (inklusive
this
) för iteratorblocket till argumentvärdena och instansvärdet som sparades när uppräkningsobjektet initierades. - Kör iteratorblocket från början tills körningen avbryts (enligt beskrivningen nedan).
- Om tillståndet för uppräkningsobjektet är körs är resultatet av att anropa
MoveNext
ospecificerat. - Om uppräkningsobjektets tillstånd är pausat anropar du MoveNext:
- Ändrar tillståndet till körande.
- Återställer värdena för alla lokala variabler och parametrar (inklusive
this
) till de värden som sparades när körningen av iteratorblocket senast pausades.Obs! Innehållet i objekt som refereras till av dessa variabler kan ha ändrats sedan föregående anrop till
MoveNext
. slutkommentar - Återupptar körningen av iteratorblocket direkt efter yield-return-uttrycket som avbröt körningen och fortsätter tills körningen avbryts (enligt beskrivningen nedan).
- Om tillståndet för uppräkningsobjektet är efter returnerar anropet
MoveNext
false.
När MoveNext
iteratorblocket körs kan körningen avbrytas på fyra sätt: Med en yield return
instruktion, med en yield break
-instruktion, genom att stöta på slutet av iteratorblocket och genom ett undantag som genereras och sprids ut ur iteratorblocket.
- När en
yield return
instruktion påträffas (§9.4.4.20):- Uttrycket som anges i instruktionen utvärderas, konverteras implicit till yield-typen och tilldelas
Current
-egenskapen i uppräkningsobjektet. - Utförandet av iteratorns kropp pausas. Värdena för alla lokala variabler och parametrar (inklusive
this
) sparas, liksom platsen för den häryield return
instruktionen. Om satsenyield return
finns inom ett eller fleratry
block, körs de associerade slutligen-blocken inte just nu. - Uppräkningsobjektets tillstånd ändras till pausat.
- Metoden
MoveNext
returnerartrue
till anroparen, vilket indikerar att iterationen framgångsrikt avancerat till nästa värde.
- Uttrycket som anges i instruktionen utvärderas, konverteras implicit till yield-typen och tilldelas
- När en
yield break
instruktion påträffas (§9.4.4.20):- Om -instruktionen
yield break
finns inom ett eller fleratry
block körs de associeradefinally
blocken. - Uppräkningsobjektets tillstånd ändras till efter.
- Metoden returnerar
MoveNext
till anroparen, vilket anger att iterationen är klar.
- Om -instruktionen
- När slutet av iteratorns brödtext påträffas:
- Uppräkningsobjektets tillstånd ändras till efter.
- Metoden returnerar
MoveNext
till anroparen, vilket anger att iterationen är klar.
- När ett undantag utlöses och sprids ut ur iteratorblocket:
- Lämpliga
finally
block i iteratorns kropp har körts av undantagsspridningen. - Uppräkningsobjektets tillstånd ändras till efter.
- Undantagsspridningen fortsätter till anroparen för
MoveNext
metoden.
- Lämpliga
15.15.5.3 Hämta det aktuella värdet
En uppräkningsobjekts egenskap påverkas Current
av yield return
-instruktioner i iteratorblocket.
Obs! Egenskapen
Current
är en synkron egenskap för både synkrona och asynkrona iteratorobjekt. slutkommentar
När ett uppräkningsobjekt är i suspenderat tillstånd, är värdet Current
det värde som angavs av det föregående anropet till MoveNext
. När ett uppräkningsobjekt finns i tillstånden före, körs eller efter är resultatet av åtkomsten Current
ospecificerat.
För en iterator med en annan avkastningstyp än object
motsvarar resultatet av att komma åt Current
genom uppräkningsobjektets IEnumerable
-implementering att komma åt Current
genom uppräkningsobjektets IEnumerator<T>
-implementering och att konvertera resultatet till object
.
15.15.5.4 Bortskaffa resurser
Metoden Dispose
eller DisposeAsync
används för att rensa iterationen genom att föra uppräkningsobjektet till eftertillståndet .
- Om uppräkningsobjektets tillstånd är före ändras tillståndet till
Dispose
när tillståndet anropas. - Om tillståndet för uppräkningsobjektet är körs är resultatet av att anropa
Dispose
ospecificerat. - Om uppräkningsobjektets tillstånd är suspenderat , anropar du
Dispose
:- Ändrar tillståndet till körande.
- Kör slutligen block som om den senast utförda
yield return
instruktionen var enyield break
-instruktion. Om detta gör att ett undantag genereras och sprids ut ur iteratortexten anges tillståndet för uppräkningsobjektet till efter och undantaget sprids till metodensDispose
anropare. - Ändrar tillståndet till efter.
- Om tillståndet för uppräkningsobjektet är efter påverkar inte anropet
Dispose
.
15.15.6 Uppräkningsbara objekt
15.15.6.1 Allmänt
När en funktionsmedlem eller lokal funktion som returnerar en uppräkningsbar gränssnittstyp implementeras med hjälp av ett iteratorblock, kör inte anropande av funktionsmedlemmen koden direkt i iteratorblocket. I stället skapas och returneras ett uppräkningsbart objekt.
Det uppräkningsbara objektets eller GetEnumerator
-metoden returnerar ett uppräkningsobjekt GetAsyncEnumerator
som kapslar in koden som anges i iteratorblocket och körningen av koden i iteratorblocket inträffar när uppräkningsobjektets eller MoveNext
-metoden anropasMoveNextAsync
. Ett uppräkningsbart objekt har följande egenskaper:
- Den implementerar
IEnumerable
ochIEnumerable<T>
ellerIAsyncEnumerable<T>
, därT
är iteratorns avkastningstyp. - Den initieras med en kopia av argumentvärdena (om några) och instansvärdet skickas till funktionsmedlemmen.
Ett uppräkningsbart objekt är vanligtvis en instans av en kompilatorgenererad uppräkningsbar klass som kapslar in koden i iteratorblocket och implementerar uppräkningsbara gränssnitt, men andra implementeringsmetoder är möjliga. Om en uppräkningsbar klass genereras av kompilatorn kommer den klassen att kapslas, direkt eller indirekt, i klassen som innehåller funktionsmedlemmen, den har privat tillgänglighet och har ett namn som är reserverat för kompilatoranvändning (§6.4.3).
Ett uppräkningsbart objekt kan implementera fler gränssnitt än de som anges ovan.
Obs! Ett uppräkningsbart objekt kan till exempel också implementera
IEnumerator
ochIEnumerator<T>
, vilket gör att det kan fungera som både en uppräkningsbar och en enumerator. Normalt returnerar en sådan implementering en egen instans (för att spara allokeringar) från det första anropet tillGetEnumerator
. Efterföljande anrop avGetEnumerator
, om sådana finns, returnerar en ny klassinstans, vanligtvis av samma klass, så att anrop till olika uppräkningsinstanser inte påverkar varandra. Den kan inte returnera samma instans även om den tidigare uppräknaren redan har räknats upp efter slutet av sekvensen, eftersom alla framtida anrop till en utmattad uppräknare måste utlösa undantag. slutkommentar
15.15.6.2 Metoden GetEnumerator eller GetAsyncEnumerator
Ett uppräkningsbart objekt tillhandahåller en implementering av metoderna från GetEnumerator
och gränssnitten IEnumerable
och IEnumerable<T>
. De två GetEnumerator
metoderna delar en gemensam implementering som hämtar och returnerar ett tillgängligt uppräkningsobjekt. Uppräkningsobjektet initieras med argumentvärdena och instansvärdet som sparades när det uppräkningsbara objektet initierades, men i övrigt fungerar uppräkningsobjektet enligt beskrivningen i §15.15.5.
Ett asynkront uppräkningsbart objekt tillhandahåller en implementering av GetAsyncEnumerator
gränssnittets IAsyncEnumerable<T>
metod. Den här metoden returnerar ett tillgängligt asynkront uppräkningsobjekt. Uppräkningsobjektet initieras med argumentvärdena och instansvärdet som sparades när det uppräkningsbara objektet initierades, men i övrigt fungerar uppräkningsobjektet enligt beskrivningen i §15.15.5.
ECMA C# draft specification