15 klasser
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 semikolon.
En klassdeklaration får inte tillhandahålla en type_parameter_constraints_clausesåvida den inte även 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 för new
att modifieraren ska visas i en klassdeklaration som inte är en kapslad 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
beskrivs i följande underklienter.
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 inte abstrakt.
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örseglade.
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-känt är
T
i en typeof_expression (§12.8.18) av bildatypeof(T)
.
En primary_expression (§12.8) är tillåten att referera till en statisk klass om
- Primary_expression
E.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
: '<' type_parameters '>'
;
type_parameters
: attributes? type_parameter
| type_parameters ',' 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 den direkta basklassen i basklassspecifikationen
X<Z.Y>
anses varaZ
, och därför (enligt reglernaobject
) inte anses ha en medlemZ
.Y
slutexempel
Basklasserna för en klass är den direkta basklassen och dess basklasser. Med andra ord är uppsättningen basklasser den transitiva stängningen 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 uppsättningen klasser som en klass är beroende av den transitiva stängningen av direkt beroende av relationen.
Exempel: Exemplet
class A : A {}
är felaktig eftersom klassen är beroende av sig själv. På samma sätt är exemplet
class A : B {} class B : C {} class C : A {}
är fel eftersom klasserna är cirkulärt beroende av sig själva. Slutligen är exemplet
class A : B.C {} class B : A { public class C {} }
resulterar i ett kompileringsfel eftersom A är
B.C
beroende av (dess direkta basklass), som ärB
beroende av (dess omedelbart omslutande klass), som cirkulärt är beroende avA
.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 går inte att härleda från en förseglad 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 Gränssnittsimplementeringar
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.
Uppsättningen gränssnitt för en typ som deklarerats i flera delar (§15.2.7) är en union av de gränssnitt som anges på varje del. Ett visst 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 deklarerats för den delen. Detta är dock 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_clauses
: type_parameter_constraints_clause
| type_parameter_constraints_clauses type_parameter_constraints_clause
;
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.
get
Precis som token och set
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änsningenunmanaged
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_annotatione* 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 sekundära begränsningar kan innehålla anteckningen av typen null. 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 null och en referenstyp som kan ogiltigförklaras.
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 nullbara typanteckningen, ?
, kan inte användas på ett argument av typen ej tränad.
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.
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-nullable struct-typer, uppräkningstyper och typparametrar som har begränsningen för värdetyp 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
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 den som den ohanterade begränsningen.
Villkoret ohanterad typ anger att ett typargument som används för typparametern ska vara en icke-nullbar ohanterad typ (§8.8).
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 för att ha begränsningen för S
värdetyp och T
för att ha referenstypsbegränsningen. 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
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 radering av en typ C
är en typ Cₓ
som konstruerats på följande sätt:
- Om
C
är en kapslad typOuter.Inner
är enCₓ
kapslad typOuterₓ.Innerₓ
. - Om
C
Cₓ
är en konstruerad typG<A¹, ..., Aⁿ>
med typargumentA¹, ..., Aⁿ
Cₓ
är den konstruerade typenG<A¹ₓ, ..., Aⁿₓ>
. - Om
C
är en matristypE[]
är matristypenCₓ
Eₓ[]
. - Om
C
är dynamiskt är detCₓ
object
. - Annars
Cₓ
ärC
.
Den effektiva basklassen för en typparameter T
definieras på följande sätt:
Låt oss R
vara en uppsättning typer som:
- För varje begränsning av
T
det är en typparameter,R
innehåller dess effektiva basklass. - För varje begränsning som
T
är en structtypR
innehållerSystem.ValueType
. - För varje begränsning som
T
är en uppräkningstypR
innehållerSystem.Enum
. - För varje begränsning som
T
är en ombudstypR
innehåller dess dynamiska radering. - För varje begränsning som
T
är en matristypR
innehållerSystem.Array
. - För varje begränsning som
T
är en klasstyp innehållerR
dess dynamiska radering.
Gäller följande
- Om
T
har begränsningen för värdetyp ärSystem.ValueType
dess effektiva basklass . - Om är tom är
R
annarsobject
den effektiva basklassen . - 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 ärT
den effektiva basklassenobject
för . 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
det inte finns någon 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 dynamiska radering 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(); }
metoderna
IPrintable
för kan anropas direkt påx
eftersomT
är begränsad till att alltid 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 Klasstext
Class_body för en klass definierar medlemmarna i den klassen.
class_body
: '{' class_member_declaration* '}'
;
15.2.7 Partiella deklarationer
Modifieraren partial
används när du definierar en klass, struct eller gränssnittstyp i flera delar. Modifieraren partial
är ett kontextuellt nyckelord (§6.4.4) och har endast särskild betydelse omedelbart före ett av nyckelorden class
, struct
eller interface
.
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 måste dock matcha, inklusive nullbarheten 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 partial
av 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 en dator som genereras av ett databasmappningsverktyg medan den andra delen skapas 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 i olika delar av en partiell deklaration 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 type_parameter s och de class_member_declarationsom omedelbart ingår i class_declaration introducera 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
Uppsättningen av medlemmar av en typ som deklareras i flera delar (§15.2.7) är föreningen 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 omslutningstypen. 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 är en typ som har partial
modifieraren.
Exempel:
partial class A { int x; // Error, cannot declare x more than once partial class Inner // Ok, Inner is a partial type { int y; } } partial class A { int x; // Error, cannot declare x more than once 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: Givet 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
I instansfunktionsmedlemmar är typen av this
instanstyp (§15.3.2) av den innehållande 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 ersätta, för varje type_parameter i medlemsdeklarationen, motsvarande type_argument för 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 offentlig medlemint
G(string s)
som inte ärvts genom att ersätta typargumentetint
för 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[]>
D<int>
genom attint
T
ersätta med 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. Medlemsernas konstituerande typer ska vara minst lika tillgängliga som den 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 förE
att ange en instans. - 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 ange 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 struct kallas för 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å typdeklarationen i vilken 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
Icke-kapslade typer kan ha public
eller internal
deklarerat tillgänglighet och har internal
deklarerat 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 typerna av deklarerad tillgänglighet och, liksom andra klassmedlemmar, standardvärdet för
private
deklarerad tillgänglighet. - En kapslad typ som deklareras i en struct kan ha någon av tre former av deklarerad tillgänglighet (
public
, , ellerinternal
) och, liksom andra struct-medlemmar, standardvärdet förprivate
private
deklarerad tillgänglighet.
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 kapslad klass
Node
.slutexempel
15.3.9.4 Döljer
En kapslad 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 den här åtkomsten
En kapslad typ och dess innehållande typ har ingen särskild relation med avseende på this_access (§12.8.14).
this
Mer specifikt kan inte en kapslad typ 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
deklarerat tillgänglighet.
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 skrivskyddad.
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 skrivskyddad.
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 (minskande) 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 |
- (unary) |
op_UnaryPlus |
+ (unary) |
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, kräver en constant_declaration varken kräver eller tillåter 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 modifierare (readonly
) eller en modifierare (volatile
), 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 för ett icke-skrivskyddat fält ändras med en tilldelning (§12.21). Värdet för ett icke-skrivskyddat fält kan både hämtas och ändras med postfixet increment and decrement operators (§12.8.16) och prefix increment and decrement operators (§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 modifierare ärreadonly
. Direkttilldelningar till skrivskyddade fält kan endast ske som en del av deklarationen eller i en instanskonstruktor eller statisk konstruktor i samma klass. (Ett skrivskyddat fält kan tilldelas flera gånger i dessa kontexter.) Mer specifikt tillåts direkttilldelningar till ett skrivskyddat fält endast i följande kontexter:
- 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. Det här är också de enda kontexter där det är giltigt att skicka ett skrivskyddat fält som en utdata- eller referensparameter.
Att försöka tilldela till ett skrivskyddat fält eller skicka det som en utdata- eller referensparameter i någon annan kontext är ett kompileringsfel.
15.5.3.2 Använda statiska skrivskyddade 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 skrivskyddade 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 kompileringstid, men när ett uttryck refererar till ett skrivskyddat fält hämtas inte värdet för fältet förrän körningen.
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 utdata från -instruktionenConsole.WriteLine
inte känt vid kompileringstid, utan hämtas i stället vid körning. 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 modifierare ärvolatile
. 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 "förvärva semantik"; det vill: det är garanterat att inträffa innan några referenser till minne som inträffar efter det i instruktionssekvensen.
- En skrivning av ett flyktigt fält kallas för en flyktig skrivning. En flyktig skrivning har "release semantik"; det vill: det är garanterat att inträffa efter eventuella minnesreferenser före skrivinstruktionen i instruktionssekvensen.
Dessa begränsningar säkerställer att alla trådar observerar flyktiga skrivningar som utförs av andra trådar i den ordning de utfördes. En implementering som överensstämmer krävs inte för att tillhandahålla en enda total ordning av flyktiga skrivningar som visas från alla körningstrådar. Typen av ett flyktigt fält ska vara något av följande:
- En reference_type.
- 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 arkivet attresult
vara synligt för huvudtråden efter arkivet 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ältinitiering
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
bådai
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. Till exempel fält motsvarar variabelinitierare tilldelningsinstruktioner som kö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 inträffar
x
när statiska fältinitierare kör och tilldelningar tilli
ochs
inträffar när instansfältets initiatorer 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. Inom varje del körs dock fältinitierarna i ordning.
Det är möjligt att statiska fält med variabelinitierare observeras i deras standardvärdetillstånd.
Exempel: Detta rekommenderas dock starkt 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 det här beteendet. 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 initiatorn föra
körningar är värdetb
för noll och initieras därföra
till1
. När initiatorn för körningar är värdet förb
en redan1
, och initieras därförb
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 generera antingen utdata:
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 båda ordningarna; de är endast begränsade att ske före referenserna till dessa fält. 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 Initiering 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'
| 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 du erkänner en method_body om både null_conditional_invocation_expression och uttrycksalternativ är tillämpliga, ska den förra 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
(§15.. 6.4), override
(§15.6.5), sealed
(§15.6.6), abstract
(§15.6.7), extern
(§15.6.8) och async
(§15.15) modifierare.
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
sealed
innehåller modifieraren innehålleroverride
deklarationen även modifieraren. - Om deklarationen
partial
innehåller modifieraren innehåller den inte någon av följande modifierare: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 är skrivskyddad. - Annars, om return_type är , är
void
och returnerar inte ett värde. - Annars returneras ett värde med metoden returns-by-value .
Den return_type av en metoddeklaration för return-by-value eller returns-no-value anger vilken typ av resultat som returneras av metoden. Endast en metod som inte returneras får innehålla partial
modifieraren (§15.6.9). Om deklarationen innehåller modifieraren async
vara eller metoden returnerar per värde och returtypen är en void
(§15.15.1).
Ref_return_type av en retur-för-referens-metoddeklaration anger vilken typ av variabel 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_clauseanger begränsningarna för typparametrarna.
En allmän method_declaration för en explicit implementering av gränssnittsmedlemmar 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 för return-by-value eller returns-no-value är antingen ett semikolon, en blocktext eller en uttryckstext. 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 för return-by-ref är antingen ett semikolon, en blocktext eller en uttryckstext. 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 constant_expression
- 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 identitet eller nullbar konvertering till parametertypen.
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
i
, är en obligatoriskref
parameter,d
är en obligatorisk värdeparameter,b
,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. Om någon av metodernas brödtext anses vara kapslad i det här deklarationsutrymmet. 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 för en metod kan parametrar refereras av deras 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
in
är modifierarna ,out
ochref
är en del av en metods signatur, men modifieraren är inte detparams
. 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 bireferensparametrar. 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 bireferensparameter ska motsvarande argument i ett metodanrop bestå av motsvarande nyckelord, , eller , följt av en in
(ref
) av samma typ som parametern.out
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.14) eller asynkron funktion (§15.15).
I en metod som tar flera bireferensparametrar är det möjligt att flera namn representerar samma lagringsplats.
15.6.2.3.2 Indataparametrar
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
x
representerari
och representerary
.j
Anropet har därför effekten att växla värdenai
för 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 utdataparameter. 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 fyra-elementint[]
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 formulär beaktas därför inte när du utför överbelastningsmatchning, och den första och tredje metodens anrop väljer därför 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
. Tvetydigheten utgör dock inga problem, eftersom den kan lösas genom att infoga en gjuten om det behövs.
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 överlagringsmatchning. 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).
I en virtuell metodanrop avgör körningstypen för den instans för vilken anropet äger rum den faktiska metodimplementeringen 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 överbelastningsmatchning på , och
C
, för att välja en specifik metodN
från den uppsättning metoder som deklareras i och ärvs avA
.M
C
Detta beskrivs i §12.8.10.2. - Sedan vid körning:
- Om
M
är en icke-virtuell metodM
anropas. - Annars
M
är en virtuell metod och den mest härledda implementeringen avM
med avseendeR
på 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 introduktionen avM
den virtuella deklarationen är detta den mest 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 -instruktionen
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
och innehåller två virtuella metoder med samma signatur: Den som introducerades avD
och den som introducerades avA
C
. 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 finnas enligt beskrivningen ovan.
- 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 åsidosättningsmetod. Med andra ord kan den åsidosatta basmetoden inte vara statisk eller icke-virtuell.
- Den åsidosatta basmetoden är inte en förseglad metod.
- Det finns en identitetskonvertering mellan returtypen för den åsidosatta basmetoden och åsidosättningsmetoden.
- Åsidosättningsdeklarationen och den åsidosatta basmetoden har samma deklarerade hjälpmedel. 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 anger inga type_parameter_constraints_clauses. 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 hadeB
skrivits((A)this).PrintFields()
anropar det rekursivt metodenPrintFields
som deklarerats iB
, inte den som deklarerats iA
, eftersomPrintFields
är virtuell och körningstypen((A)this)
ärB
.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ålleroverride
ingen modifierare och åsidosätterF
därför inte metoden 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 tillåts indeklarationenF
C
att åsidosätta ä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
modifieraren förhindrarC
att ytterligare åsidosätta .F
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 krävs icke-abstrakta härledda klasser för att tillhandahålla en egen implementering genom att åsidosätta den metoden. 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) för att referera 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. Den partiella identifieraren 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 kan det bara finnas en som definierar partiell metoddeklaration med en viss signatur, och det kan bara finnas en som implementerar partiell metoddeklaration med en viss signatur. Om en ofullständig metoddeklaration för genomförande anges ska det finnas en motsvarande definition av partiell metoddeklaration och deklarationerna ska överensstäma enligt vad som anges i 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 identitets konvertibla typer (moduloskillnader 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 överbelastningsmatchning. Oavsett om en implementeringsdeklaration ges eller inte kan anropsuttryck matchas mot anrop av den partiella metoden. Eftersom en partiell metod alltid returnerar void
är sådana anropsuttryck alltid uttrycksuttryck. Eftersom en partiell metod är implicit private
sker sådana instruktioner alltid inom en av de delar av typdeklarationen inom vilken 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. Anropsuttrycket, inklusive eventuella underuttryck, har därför 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 kompileringsfel att skapa ett ombud från
M
(§12.8.17.6).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 får 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.15.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.15.1) är T
.
När den effektiva returtypen för en metod är void
och metoden har ett blocktext, return
ska instruktioner (§13.10.5) i blocket inte ange något uttryck. Om körningen av blocket för en void-metod slutförs normalt (det vill säga styra flöden från slutet av metodtexten) å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 för return-by-value och return-by-ref ska metodkroppens slutpunkt inte kunna nås. Med andra ord är kontrollen inte tillåten att flöda från slutet av metodtexten.
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ärdereturerande
F
metoden resulterar i ett kompileringsfel eftersom kontrollen kan flöda från slutet av metodtexten. MetodernaG
ochH
är korrekta eftersom alla möjliga körningssökvägar slutar med en retursats som anger ett returvärde. MetodenI
är korrekt eftersom dess brödtext motsvarar ett block med bara en enda 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 ett naturligt tillägg för fält – båda heter 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. I stället har egenskaper åtkomst som anger vilka instruktioner som ska köras när deras värden läse 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'
| 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).
Det finns två typer av property_declaration:
- Den första deklarerar en icke-referensvärdesegenskap. Dess värde har typtyp. Den här typen av egenskap kan vara läsbar och/eller skrivbar.
- Den andra deklarerar en referensvärdesegenskap. Dess värde är en variable_reference (§9.5
readonly
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 uttryckstext eller en uttryckstext. I ett instruktionsorgan deklarerar accessor_declarations, som ska omges av "{
" och "}
" tokens, tillgångsgivarna (§15.7.3) av fastigheten. Åtkomsterna anger 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 en uttryckstext eller en uttryckstext. I en instruktionstext deklarerar en get_accessor_declaration get-accessoren (§15.7.3) av fastigheten. Accessorn anger de körbara instruktioner som är associerade med att läsa egenskapen.
I en { 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
, , ellerout
argument om inte egenskapen är referensvärdevärde och därför returnerar en variabelreferens (ref
). 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 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ätter indexerare/indexerare för egendom/egenskaper och läser listan över skillnader mellan egenskaper och indexerare som anges i §15.9.2. slutkommentar
Accessor_declarations för en egenskap anger de körbara instruktioner som är associerade med 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'
;
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.
- 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 någon, av 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 egendom ska ha minst en get-accessor. För åtkomst 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 accessor för andra icke-abstrakta, icke-externa egenskaper ä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ärdesegendom ska överensstämma med reglerna för värdereturmetoder 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 kunna nås.
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 variable_references, 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
En get-accessor för en referensvärdesegendom ska överensstämma med reglerna för referensvärdemetoder som beskrivs i §15.6.11.
En uppsättningsåtkomstor motsvarar en metod med en enda värdeparameter av egenskapstypen och en void
returtyp. Den implicita parametern för en uppsättningsåtkomstor heter value
alltid . 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). Organet för en fast accessor ska följa de regler för void
metoder som beskrivs i §15.6.11. I synnerhet är returinstruktioner i uppsättningens accessor-brödtext inte tillåtna att ange ett uttryck. Eftersom en uppsättningsåtkomstor implicit har en parameter med namnet value
är det ett kompileringsfel för att en lokal variabel eller konstant deklaration i en uppsättningsåtkomst ska ha det namnet.
Baserat på förekomsten eller frånvaron av get- och set-åtkomsterna klassificeras en egenskap enligt följande:
- En egenskap som innehåller både en get-accessor och en set-accessor sägs vara en skrivskyddad egenskap.
- En egenskap som bara har en get-accessor sägs vara en skrivskyddad egenskap. Det är ett kompileringsfel för att en skrivskyddad egenskap ska vara målet för en tilldelning.
- En egenskap som bara har en angivet accessor sägs vara en skrivskyddad egenskap. Förutom som mål 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å skrivskyddade egenskaper, eftersom dessa operatorer läser det gamla värdet för sin operande innan de skriver den 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-accessorn för egenskapen Caption returnerar denstring
som lagras i det privatacaption
fältet. Den inställda accessorn kontrollerar om det nya värdet skiljer sig från det aktuella värdet, och i så fall lagrar den det nya värdet och ommålar 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
Hämta och ange åtkomst till en egenskap är inte distinkta medlemmar och det går inte att deklarera åtkomsterna 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 skrivskyddad egenskap. I stället deklareras två egenskaper med samma namn, en skrivskyddad och en skrivskyddad. 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
medA
avseende på både läsning och skrivning. I satsernaB b = new B(); b.P = 1; // Error, B.P is read-only ((A)b).P = 1; // Ok, reference to A.P
tilldelningen gör att
b.P
ett kompileringsfel rapporteras, eftersom den skrivskyddadeP
egenskapen iB
döljer egenskapen skrivskyddadP
iA
. Observera dock att en gjuten 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
egenskap ochY
som enLocation
egenskap av typenPoint
. Om det i en framtida version avLabel
blir enklare att lagra platsen interntPoint
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 motsvarar att anropa en get-accessor för att läsa värdet för ett fält anses det vara dåligt programmeringsformat för att få åtkomst till observerbara biverkningar. 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-användare innebär inte att få-åtkomst bör alltid skrivas bara 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 refererarOut
till egenskapen, som iConsole.Out.WriteLine("hello, world");
den underliggande
TextWriter
för utdataenheten 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ärdesegenskap med semikolonbegränsade accessor_bodys. Autoegenskaper ska ha en get-accessor och kan eventuellt ha en fast 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 uppsättningsåtkomst anses bakgrundsfältet readonly
(§15.5.3). Precis som ett readonly
fält kan en skrivskyddad automatisk egenskap också tilldelas i brödtexten för en konstruktor i den omslutande klassen. En sådan tilldelning tilldelar direkt till det skrivskyddade bakgrundsfältet för egenskapen.
En automatisk egenskap kan eventuellt ha en property_initializer, som tillämpas direkt på bakgrundsfä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 i 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 tillgänglighetsdomänen (§7.5.3) för användaren 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 överbelastningsmatchning (§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 målet för sammansatt tilldelning (§12.21.4), eller som mål
++
för eller--
operatörerna (§12.8.16, §12.9.6), ska både get-accessorerna och den inställda 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; tillgänglighetsdomänen för den aktuella get-accessorn används 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 användaren 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, åsidosättnings- 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ätter indexerare/indexerare för egendom/egenskaper och läser listan över skillnader mellan egenskaper och indexerare som anges i §15.9.2. slutkommentar
En virtuell egenskapsdeklaration anger att egenskapens åtkomst är virtuell. Modifieraren virtual
gäller för alla icke-privata accessorer för en egenskap. När en åtkomst till en virtuell egenskap har accessor_modifier private
är den privata accessorn implicit inte virtuell.
En abstrakt egenskapsdeklaration anger att egenskapens accessorer är virtuella, men inte tillhandahåller någon faktisk implementering av åtkomstgivarna. I stället krävs icke-abstrakta härledda klasser för att tillhandahålla en egen implementering för åtkomstgivarna 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 egendom 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. Åtkomsten till en sådan egenskap är också abstrakt.
Abstrakta egenskapsdeklarationer tillåts endast i abstrakta klasser (§15.2.2.2). Åtkomsten till en ärvd virtuell egenskap kan åsidosättas i en härledd klass genom att inkludera en egenskapsdeklaration som anger ett override
direktiv. Detta kallas för en åsidosättande egenskapsdeklaration. En åsidosättande egenskapsdeklaration deklarerar inte en ny egenskap. I stället specialiserar den sig helt enkelt på implementeringarna av accessorerna för en befintlig virtuell egenskap.
Åsidosättningsdeklarationen och den åsidosatta basegenskapen måste ha samma deklarerade hjälpmedel. Med andra ord ska en åsidosättningsdeklaration inte ändra tillgängligheten för basegenskapen. 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 skrivskyddad) 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 skrivskyddad) kan den överordnade egenskapen innehålla antingen en enskild åtkomst eller båda åtkomsterna. Det ska ske en identitetskonvertering mellan den åsidosättande och ärvda egendomens typ.
En åsidosättande egenskapsdeklaration kan innehålla sealed
modifieraren. Användning av den här modifieraren förhindrar att en härledd klass ytterligare åsidosättar egenskapen. Accessorerna 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 uppsättningsåtkomstör motsvarar en metod med en enda värdeparameter av egenskapstypen, en returtyp för tomrum och samma modifierare som den innehållande 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 skrivskyddad egenskap ochZ
är en abstrakt skrivskyddad egenskap. EftersomZ
det är abstrakt ska den innehållande klass A också förklaras 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
åsidosättande av egenskapsdeklarationer. Varje egenskapsdeklaration matchar exakt hjälpmedelsmodifierarna, typen och namnet på motsvarande ärvda egenskap. Get-accessorX
för och set-accessor förY
att använda 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 åsidosättning ska alla åsidosättningsåtkomster vara tillgängliga för den övergripande koden. Dessutom ska den deklarerade tillgängligheten för både egendomen eller indexeraren själv, och för accessorerna, överensstäma med den åsidosättna 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'
| 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.
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 fel att den extern
inkluderar både modifieraren och event_accessor_declarations.
Det är ett kompileringsfel för en variable_declarator av en händelsedeklaration med en abstract
eller external
modifierare för att inkludera en variable_initializer.
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.
I en åtgärd av formuläret x += y
eller , när x –= y
är en händelse som resultatet av åtgärden har typ x
(void
) (i stället för att ha typen av , med värdet x
för efter tilldelningen, som för andra x
operatorer och +=
definierade 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 ombudsanropet föregås av en kontroll som säkerställer att ombudet inte är null och att kontrollen görs på en lokal kopia för att säkerställa trådsäkerheten.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 av formuläret:
class X { public event D Ev; }
ska sammanställas till något 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
gör referenser tillEv
vänster om+=
operatorerna och–=
att tilläggs- och borttagningsåtkomsterna 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 Händelseåtkomster
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 add_accessor_declaration och en remove_accessor_declaration. Varje åtkomstdeklaration består av tokentillägget eller borttagningen 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ändelsetilldelning används lämplig händelseåtkomstor. Mer specifikt, om tilldelningsoperatorn är +=
så används tilläggsåtkomstorn, och om tilldelningsoperatorn är –=
så används ta bort-accessorn. I båda fallen används rätt operand för tilldelningsoperatorn som argument för händelseåtkomstorn. 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 return
är instruktioner i ett sådant block inte tillåtna att ange ett uttryck.
Eftersom en händelseåtkomstor implicit har en parameter med namnet value
är det ett kompileringsfel för en lokal variabel eller konstant som deklarerats i en händelseåtkomstor för 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
i en statisk händelses åtkomst.
En instanshändelse är associerad med en viss instans av en klass och den här instansen kan nås som this
(§12.8.14) i den händelsens åtkomst.
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 krävs icke-abstrakta härledda klasser för att tillhandahålla sin egen implementering för åtkomstgivarna 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. Åtkomsten till en sådan händelse är också abstrakt.
Abstrakta händelsedeklarationer tillåts endast i abstrakta klasser (§15.2.2.2).
Åtkomsten till en ärvd virtuell händelse kan åsidosättas i en härledd klass genom att inkludera en händelsedeklaration som anger en override
modifierare. 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 åtkomst till en befintlig virtuell händelse.
En åsidosättande händelsedeklaration ska ange exakt samma hjälpmedelsmodifierare och namn som den åsidosätter händelsen, det ska finnas en identitetskonvertering mellan typen av åsidosättande och den åsidosatta händelsen, och både tilläggs- och borttagningsåtkomsterna 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. Accessorerna 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'
| 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).
Det finns två typer av indexer_declaration:
- Den första deklarerar en icke-referensvärdesindexerare. Dess värde har typtyp. Den här typen av indexerare kan vara läsbar och/eller skrivbar.
- Den andra deklarerar en referensvärdesindexerare. Dess värde är en variable_reference (§9.5
readonly
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 gränssnittsmedlemmar följs typen av nyckelordet this
. För en explicit implementering av gränssnittsmedlemmar följs typen av en interface_type, 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 ett instruktionsorgan deklarerar accessor_declarations, som ska omges av "{
" och "}
" token, indexerarens (§15.7.3) åtkomst (§15.7.3). Åtkomsterna anger de körbara instruktioner som är associerade med element för läsning och skrivning av indexerare.
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 uttryckstext eller en uttryckstext. I en instruktionstext deklarerar en get_accessor_declaration indexerarens get-accessor (§15.7.3). Accessorn anger de körbara instruktioner som är associerade med att läsa indexeraren.
I en { 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 skicka ett indexerarelement som ett
in
,out
ellerref
argument om inte indexeraren är referensvärdevä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[]
.I följande
CountPrimes
klass används enBitArray
och den klassiska "siktalgoritmen" 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. Den första parametern måste vara en versal eller gemen bokstav i intervallet A–Z, och den andra måste vara ett heltal i intervallet 0–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 indexerare att deklarera en lokal variabel eller lokal konstant med samma namn som en indexerareparameter.
- I en åsidosättande egenskapsdeklaration används den ärvda egenskapen med hjälp av 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 fastigheter med indexerare/indexerare vid läsning av §15.7.3, §15.7.5 och §15.7.6 gäller även för definierade termer. Mer specifikt blir skrivskyddad egenskap skrivskyddad indexerare, skrivskyddad egenskap blir skrivskyddad indexerare och skrivskyddad egenskap blir skrivskyddad indexerare.
15.10 Operatorer
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 överlagbar 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. - Parametern eller parametrarna för en operatör får inte ha några 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 underetiketter.
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 operatorer
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 operande 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 (>>
) ska ha två parametrar, varav den första ska ha typ eller T? och den andra ska ha typT
ellerint
, och kan returnera vilkenint?
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 operatorn för paret. Två operatordeklarationer matchar om identitetskonverteringar finns 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 konverteringar kan ske i en mängd olika situationer, inklusive funktionsmedlemsanrop, cast-uttryck 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åter S₀
och T₀
refererar du till deras underliggande typer, annars S₀
och T₀
är 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 eller
S₀
T₀
ä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 uttryckstext motsvarar exakt blocket för en instansmetod med en void
returtyp (§15.6.11).
Instanskonstruktorer ärvs 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 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 deklarerats i dess klass. Detta motsvarar en sekvens med tilldelningar som körs omedelbart vid inmatning till konstruktorn och före den implicita anropet av den direkta basklasskonstruktorn. Variabelinitierarna körs i textordningen där de visas i klassdeklarationen (§15.5.6).
15.11.4 Konstruktorkö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 Standardkonstruktorer
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 av formuläret
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.
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.
Om du vill initiera en ny typ av stängd 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 initieras till standardvärdet (§15.5.5). Därefter körs de statiska fältinitierarna (§15.5.6.2) för dessa statiska fält. Slutligen körs den statiska konstruktorn.
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 initializer görA
att konstruktornstatic
körs eftersom värdetA.X
för refereras. Den statiska konstruktornA
för i sin tur fortsätter att beräkna värdetX
för , 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 Finalizers
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 determinat sätt, medan en finalator i C#inte ä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.
Finalizers ärvs inte. En klass har alltså inga andra finalizers än 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
Slutförare anropas automatiskt och kan inte anropas explicit. 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 har slutförts anropas slutförarna i den instansens arvskedja i ordning från de flesta härledda till 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 kallas i beställa, från mest härledd till least härledd.
slutexempel
Finalizers implementeras genom att åsidosä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
metoden .Finalize
slutexempel
För en diskussion om beteendet när ett undantag utlöses från en finalator, se §21.4.
15.14 Iteratorer
15.14.1 Allmänt
En funktionsmedlem (§12.6) som implementeras med hjälp av ett iteratorblock (§13.3) kallas 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.14.2) eller något av de uppräkningsbara gränssnitten (§15.14.3). Det kan inträffa som en method_body, operator_body eller accessor_body, medan händelser, instanskonstruktorer, statiska konstruktorer och slutförare inte ska implementeras som iteratorer.
När en funktionsmedlem implementeras med hjälp av ett iteratorblock är det ett kompileringsfel för parameterlistan för funktionsmedlemmen för att ange några in
, out
eller ref
parametrar eller en parameter av en ref struct
typ.
15.14.2 Uppräkningsgränssnitt
Uppräkningsgränssnitten är det icke-generiska gränssnittet och alla instansier av det generiska gränssnittet .System.Collections.Generic.IEnumerator<T>
För korthets skull refereras dessa gränssnitt i den här underklienten och dess syskon till som IEnumerator
respektive IEnumerator<T>
.
15.14.3 Uppräkningsbara gränssnitt
De uppräkningsbara gränssnitten är det icke-generiska gränssnittet System.Collections.IEnumerable
och alla instansieringar av det generiska gränssnittet System.Collections.Generic.IEnumerable<T>
. För korthets skull refereras dessa gränssnitt i den här underklienten och dess syskon till som IEnumerable
respektive IEnumerable<T>
.
15.14.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
IEnumerator<T>
ellerIEnumerable<T>
ärT
.
15.14.5 Uppräkningsobjekt
15.14.5.1 Allmänt
När en funktionsmedlem som returnerar en uppräkningsgränssnittstyp implementeras med hjälp av ett iteratorblock, körs inte koden omedelbart i iteratorblocket när funktionsmedlemmen 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 sker när uppräkningsobjektets metod anropas MoveNext
. Ett uppräkningsobjekt har följande egenskaper:
- Den implementerar
IEnumerator
ochIEnumerator<T>
, därT
är iteratorns avkastningstyp. - Den implementerar
System.IDisposable
. - 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 underpaneler beskriver det nödvändiga beteendet MoveNext
för , Current
och Dispose
medlemmar i IEnumerator
implementeringarna och IEnumerator<T>
gränssnittet som tillhandahålls av ett uppräkningsobjekt.
Uppräkningsobjekt stöder IEnumerator.Reset
inte metoden. Om du anropar den här metoden genereras en System.NotSupportedException
.
15.14.5.2 Metoden MoveNext
Metoden MoveNext
för ett uppräkningsobjekt kapslar in koden för ett iteratorblock. När MoveNext
metoden anropas körs kod i iteratorblocket och egenskapen för uppräkningsobjektet anges Current
efter behov. Den exakta åtgärd som utförs av MoveNext
beror på tillståndet för uppräkningsobjektet när MoveNext
anropas:
- Om uppräkningsobjektets tillstånd är tidigare anropar du
MoveNext
:- Ändrar tillståndet till att köras.
- 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 körs är resultatet av att
MoveNext
anropa ospecificerat. - Om uppräkningsobjektets tillstånd är pausat anropar du MoveNext:
- Ändrar tillståndet till att köras.
- Å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 omedelbart efter return-instruktionen för avkastning som orsakade att körningen avbröts och fortsätter tills körningen avbryts (enligt beskrivningen nedan).
- Om tillståndet för uppräkningsobjektet är efter returnerar
MoveNext
anropet 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 avkastningstypen och tilldelas egenskapen för
Current
uppräkningsobjektet. - Körningen av iteratortexten pausas. Värdena för alla lokala variabler och parametrar (inklusive
this
) sparas, liksom platsen för den häryield return
instruktionen. Om -instruktionenyield return
finns inom ett eller fleratry
block körs inte de associerade slutligen-blocken just nu. - Uppräkningsobjektets tillstånd ändras till pausat.
- Metoden
MoveNext
återgårtrue
till anroparen, vilket anger att iterationen har avancerat till nästa värde.
- Uttrycket som anges i -instruktionen utvärderas, konverteras implicit till avkastningstypen och tilldelas egenskapen för
- 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
MoveNext
återgårfalse
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
MoveNext
återgårfalse
till anroparen, vilket anger att iterationen är klar.
- När ett undantag utlöses och sprids ut ur iteratorblocket:
- Lämpliga
finally
block i iteratortexten har körts av undantagsspridningen. - Uppräkningsobjektets tillstånd ändras till efter.
- Undantagsspridningen fortsätter till anroparen för
MoveNext
metoden.
- Lämpliga
15.14.5.3 Aktuell egenskap
En uppräkningsobjekts egenskap påverkas Current
av yield return
-instruktioner i iteratorblocket.
När ett uppräkningsobjekt är i pausat tillstånd är värdet Current
för värdet som angavs av föregående anrop 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 åtkomst Current
via uppräkningsobjektets IEnumerable
implementering Current
åtkomst via uppräkningsobjektets IEnumerator<T>
implementering och gjutning av resultatet till object
.
15.14.5.4 Metoden Förfoga över
Metoden Dispose
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 körs är resultatet av att
Dispose
anropa ospecificerat. - Om uppräkningsobjektets tillstånd pausas anropar du
Dispose
:- Ändrar tillståndet till att köras.
- 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.14.6 Uppräkningsbara objekt
15.14.6.1 Allmänt
När en funktionsmedlem som returnerar en uppräkningsbar gränssnittstyp implementeras med hjälp av ett iteratorblock, kör anropande av funktionsmedlemmen inte koden omedelbart i iteratorblocket. I stället skapas och returneras ett uppräkningsbart objekt. Det uppräkningsbara objektets metod returnerar ett uppräkningsobjekt GetEnumerator
som kapslar in koden som anges i iteratorblocket och körningen av koden i iteratorblocket inträffar när uppräkningsobjektets metod anropas MoveNext
. Ett uppräkningsbart objekt har följande egenskaper:
- Den implementerar
IEnumerable
ochIEnumerable<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 uppräkning. 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.14.6.2 Metoden GetEnumerator
Ett uppräkningsbart objekt tillhandahåller en implementering av GetEnumerator
metoderna och IEnumerable
gränssnittenIEnumerable<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 annars fungerar uppräkningsobjektet enligt beskrivningen i §15.14.5.
15.15 Async Functions
15.15.1 Allmänt
En metod (§15.6) eller anonym funktion (§12.19) med modifieraren kallas för enasync
. I allmänhet används termen asynkron för att beskriva alla typer av funktioner som har async
modifieraren.
Det är ett kompileringsfel för parameterlistan för en asynkron funktion för att ange valfri in
, , eller out
parametrar eller valfri parameter av en ref
ref struct
typ.
Return_type för en asynkron metod ska vara antingen eller en void
. För en asynkron metod som ger ett resultatvärde ska en aktivitetstyp vara allmän. 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 skapas från System.Threading.Tasks.Task<TResult>
är aktivitetstyper, samt en klass-, struct- eller gränssnittstyp 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 aktivitetstyp sägs vara uppgiftsretur.
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. En lyckad«TaskType»<T>
post registrerar ett resultat av typen T
. Uppgiftstyper är väntande och aktiviteter kan därför vara operander för väntande uttryck (§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.15.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 aktivitets buider-typen också vara kapslad i samma typ. slutkommentar
En asynkron funktion har möjlighet att pausa utvärderingen med hjälp av inväntningsuttryck (§12.9.8) i kroppen. Utvärderingen kan senare återupptas vid tidpunkten för inväntningsuttrycket med hjälp av ett återtagandedelegat. Återtagandedelegaten är av typen System.Action
, och när den anropas återupptas utvärderingen av anropet av funktionen async från det väntande uttrycket där det slutade. Den aktuella anroparen för en asynkron funktionsanrop är den ursprungliga anroparen om funktionsanropet aldrig har pausats eller om den senaste anroparen av återupptagningsdelegeringen annars.
15.15.2 Byggmönster av aktivitetstyp
En aktivitetsbyggare kan ha högst en typparameter och kan inte kapslas i en allmän 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 antingen ringa
stateMachine.MoveNext()
inStart()
eller efterStart()
att ha återvänt för att föra tillståndsmaskinen framåt.
- Byggverktyget ska antingen ringa
- När
Start()
den har returneratsasync
anropasbuilder.Task
metoden för att aktiviteten ska returneras från async-metoden. - Varje anrop till
stateMachine.MoveNext()
för vidare tillståndsdatorn. - Om tillståndsdatorn har slutförts
builder.SetResult()
anropas, med metodens returvärde, om det finns några. - Annars anropas om ett undantag
e
genereras i tillståndsdatornbuilder.SetException(e)
. - Om tillståndsdatorn når ett
await expr
uttryckexpr.GetAwaiter()
anropas. - Om awaiter implementerar
ICriticalNotifyCompletion
ochIsCompleted
är falskt anroparbuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine)
tillståndsdatorn .-
AwaitUnsafeOnCompleted()
ska anropaawaiter.UnsafeOnCompleted(action)
med ettAction
som anroparstateMachine.MoveNext()
när inväntaren är klar.
-
- Annars anropar
builder.AwaitOnCompleted(ref awaiter, ref stateMachine)
tillståndsdatorn .-
AwaitOnCompleted()
ska anropaawaiter.OnCompleted(action)
med ettAction
som anroparstateMachine.MoveNext()
när inväntaren är klar.
-
-
SetStateMachine(IAsyncStateMachine)
kan anropas av den kompilatorgenererade implementeringen för att identifiera instansen av byggaren som är associeradIAsyncStateMachine
med en tillståndsdatorinstans, särskilt i fall där tillståndsdatorn implementeras som en värdetyp.- Om byggaren anropar
stateMachine.SetStateMachine(stateMachine)
stateMachine
anropasbuilder.SetStateMachine(stateMachine)
den builder-instans som är associerad medstateMachine
.
- Om byggaren anropar
Obs! För både
SetResult(T result)
och«TaskType»<T> Task { get; }
måste parametern respektive argumentet vara identitets cabriolet tillT
. På så sätt kan en byggare av aktivitetstyp stödja typer som tupplar, där två typer som inte är samma är identitetskonverterbara. slutkommentar
15.15.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 returaktiviteten för funktionen async. Aktiviteten är ursprungligen i ett ofullständigt tillstånd.
Asynkron funktionstext utvärderas sedan tills den antingen pausas (genom att nå ett await-uttryck) eller avslutas, då kontrollen returneras till anroparen, tillsammans med returaktiviteten.
När asynkroniseringsfunktionens brödtext avslutas flyttas returaktiviteten från det ofullständiga tillståndet:
- Om funktionstexten avslutas som ett resultat av att en returinstruktur eller slutet av brödtexten har nåtts registreras ett resultatvärde i returaktiviteten, som försätts i ett lyckat tillstånd.
- Om funktionstexten avslutas på grund av ett ouppklarat
OperationCanceledException
registreras undantaget i returaktiviteten som försätts i det avbrutna tillståndet. - Om funktionstexten avslutas till följd av något annat undantag (§13.10.6) registreras undantaget i returuppgiften som försätts i feltillstånd.
15.15.4 Utvärdering av en asynkron asynkron funktion som returneras
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 eller gör att ett ohanterade 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 som ska spridas ska spridas ut ur dem.
ECMA C# draft specification