Dela via


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, internaloch 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, internaloch 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, sealedoch 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 antingen null 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 metod F. Klassen B introducerar ytterligare en metod G, men eftersom den inte tillhandahåller någon implementering av F, B ska den också förklaras abstrakt. Klassen C åsidosätter F och tillhandahåller en faktisk implementering. Eftersom det inte finns några abstrakta medlemmar i CC 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 eller abstract 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 protectedeller protected 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äret T.I, eller
  • Det namespace_or_type-känt är T i en typeof_expression (§12.8.18) av bilda typeof(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 objectbasklassen . 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ör B, och B sägs härledas från A. Eftersom A inte uttryckligen anger en direkt basklass är dess direkta basklass implicit object.

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 vara B<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.ValueTypeeller 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 Bantas 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 vara Z, och därför (enligt reglerna object) inte anses ha en medlem Z.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 är C<int[]>, B<IComparable<int[]>>, Aoch object.

slutexempel

Förutom klass objecthar 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 är B beroende av (dess omedelbart omslutande klass), som cirkulärt är beroende av A.

slutexempel

En klass är inte beroende av de klasser som är kapslade i den.

Exempel: I följande kod

class A
{
    class B : A {}
}

B beror på A (eftersom A är både dess direkta basklass och dess omedelbart omslutande klass), men A är inte beroende av B (eftersom B är varken en basklass eller en omslutande klass av A). 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 klassen A.

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 är IA, IBoch IC.

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 eller T : BaseClass), utan använder T? 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ör T. Således är rekursivt konstruerade typer av formulär T?? och Nullable<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 eller System.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 typparametern S beror detS på.T
  • Om en typparameter S är beroende av en typparameter T och T är beroende av en typparameter U beror det SU.

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. Annars T är effektivt förseglad så S skulle tvingas vara samma typ som T, vilket eliminerar behovet av två typparametrar.
  • Om S har begränsningen för värdetyp ska den T inte ha något class_type villkor.
  • Om S har en class_type begränsning A och T har en class_type villkor B ska det finnas en identitetskonvertering eller implicit referenskonvertering från A till B eller en implicit referenskonvertering från B till A.
  • Om S också är beroende av typparameter och UU har en class_type begränsning A och T har en class_type villkor B ska det finnas en identitetskonvertering eller implicit referenskonvertering från A till B eller en implicit referenskonvertering från B till A.

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.Enumoch 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 typ Outer.Inner är en Cₓ kapslad typ Outerₓ.Innerₓ.
  • Om CCₓär en konstruerad typ G<A¹, ..., Aⁿ> med typargument A¹, ..., AⁿCₓ är den konstruerade typen G<A¹ₓ, ..., Aⁿₓ>.
  • Om C är en matristyp E[] är matristypen CₓEₓ[].
  • Om C är dynamiskt är det Cₓobject.
  • Annars Cₓ är C.

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 structtyp R innehåller System.ValueType.
  • För varje begränsning som T är en uppräkningstyp R innehåller System.Enum.
  • För varje begränsning som T är en ombudstyp R innehåller dess dynamiska radering.
  • För varje begränsning som T är en matristyp R innehåller System.Array.
  • För varje begränsning som T är en klasstyp innehåller R dess dynamiska radering.

Gäller följande

  • Om T har begränsningen för värdetyp är System.ValueTypedess effektiva basklass .
  • Om är tom är Rannars object den effektiva basklassen .
  • Annars är den effektiva basklassen T av den mest omfattande typen (§10.5.3) av uppsättningen R. Om uppsättningen inte har någon omfattande typ är Tden effektiva basklassen object 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 eftersom T är begränsad till att alltid implementera IPrintable.

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, structeller 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 infrån , outoch ref.

  • 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 och out.

  • 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 klassdeklarationen Gen är "tvådimensionell matris av T", så typen av medlemmen a i den konstruerade typen ovan är "tvådimensionell matris av endimensionell matris av int", eller int[,][].

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ån B, och B härleds från A, ärver de C medlemmar som deklareras i B samt de medlemmar som deklareras i A.

  • 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 medlem intG(string s) som inte ärvts genom att ersätta typargumentet int för typparametern T. D<int> har också en ärvd medlem från klassdeklarationen B. Den här ärvda medlemmen bestäms genom att först fastställa basklasstypen B<int[]>D<int> genom att intT ersätta med i basklassspecifikationen B<T[]>. Sedan, som ett typargument till B, int[] ersätts med U i public U F(long index), vilket ger den ärvda medlemmen public 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, internaleller 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äret E.Mska E en typ som har en medlem Manges . Det är ett kompileringsfel för E 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äret E.M, E ska en instans av en typ som har en medlem Manges. 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. Metoden G visar att i en statisk funktionsmedlem är det ett kompileringsfel att komma åt en instansmedlem via en simple_name. Metoden Main 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 klassen A, och klassen A ä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, , eller internal) och, liksom andra struct-medlemmar, standardvärdet för privateprivatedeklarerad 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 metoden M som definierats i Base.

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 av Nestedoch skickar sin egen till Nestedkonstruktorn för att ge efterföljande åtkomst till Cinstansmedlemmar.

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 klass Nested. I Nestedanropar metoden G den statiska metoden F som definierats i Coch F 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 metoden F som definierats i Derivedbasklassen , Basegenom att anropa via en instans av Derived.

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:

  1. 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.
  2. 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.
  3. 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 egenskap P, vilket reserverar signaturer för get_P och set_P metoder. A -klassen B härleds från A 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, charfloat, double, decimal, bool, en stringenum_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åten new i en constant_expression, är det enda möjliga värdet för konstanter av stringandra än null . 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 och readonly 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ärdera B.Zoch slutligen utvärdera A.X, producera värdena 10, 11och 12.

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 och B deklarerades i separata program, skulle det vara möjligt för A.X att vara beroende B.Zav , men B.Z kan då inte samtidigt vara beroende A.Yav . 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;
    }
}

Blackmedlemmarna , White, Red, Greenoch Blue kan inte deklareras som const-medlemmar eftersom deras värden inte kan beräknas vid kompileringstid. Men att deklarera dem static 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 och Program2 anger två program som kompileras separat. Eftersom Program1.Utils.X deklareras som ett static readonly fält är värdet som utdata från -instruktionen Console.WriteLine inte känt vid kompileringstid, utan hämtas i stället vid körning. Om värdet X för ändras och Program1 omkompileras kommer instruktionen Console.WriteLine därför att mata ut det nya värdet även om Program2 det inte är omkompilerat. Men om det hade X varit en konstant skulle värdet X för ha erhållits vid den tidpunkten Program2 kompilerades och skulle förbli opåverkad av ändringar i Program1 tills Program2 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, , sbyteshort, ushort, int, uint, , char, float, bool, System.IntPtreller System.UIntPtr.
  • En enum_type som har en enum_base typ av byte, sbyte, short, ushort, inteller uint.

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 metoden Thread2. Den här metoden lagrar ett värde i ett icke-flyktigt fält med namnet resultoch lagrar true sedan i det flyktiga fältet finished. Huvudtråden väntar tills fältet finished har angetts till trueoch läser sedan fältet result. Eftersom finished har deklarerats volatileska huvudtråden läsa värdet 143 från fältet result. Om fältet finished inte hade deklarerats volatileskulle det vara tillåtet för arkivet att result vara synligt för huvudtråden efter arkivet till finished, och därmed för huvudtråden att läsa värdet 0 från fältet result. Om du deklarerar finished som ett volatile 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åda i 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 till i och s 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 och bär programmet giltigt. Det resulterar i utdata

a = 1, b = 2

eftersom de statiska fälten a och b initieras till 0 (standardvärdet för int) innan deras initialiserare körs. När initiatorn för a körningar är värdet b för noll och initieras därför a till 1. När initiatorn för körningar är värdet för b en redan 1, och initieras därför b till 2.

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 och Y"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ärmed B's statiska fältinitierare) ska köras före A'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, virtualoch override.
  • Deklarationen innehåller högst en av följande modifierare: new och override.
  • Om deklarationen abstract innehåller modifieraren innehåller deklarationen inte någon av följande modifierare: static, virtual, sealedeller extern.
  • Om deklarationen private innehåller modifieraren innehåller deklarationen inte någon av följande modifierare: virtual, overrideeller abstract.
  • Om deklarationen sealed innehåller modifieraren innehåller override deklarationen även modifieraren.
  • Om deklarationen partial innehåller modifieraren innehåller den inte någon av följande modifierare: new, , publicprotected, internal, private, virtual, sealed, override, , abstracteller extern.

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, outoch 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 structthis 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är S är en värdetyp
  • ett uttryck för formuläret default(S) där S ä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 Mi , är en obligatorisk ref parameter, d är en obligatorisk värdeparameter, b, so , och t är valfria värdeparametrar och a ä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:

Obs! Enligt beskrivningen i in är modifierarna , outoch ref ä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 i Mainxrepresenterar i och representerar y.j Anropet har därför effekten att växla värdena i för och j.

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 i G skickar en referens till s för både a och b. För det anropet refererar alltså namnen s, aoch b alla till samma lagringsplats, och de tre tilldelningarna ändrar alla instansfältet s.

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 och name kan tas bort innan de skickas till SplitPathoch 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[] och string[][] kan användas som typ av en parametermatris, men typen string[,] kan inte göra det. slutexempel

Obs! Det går inte att kombinera params modifieraren med modifierarna in, outeller ref. 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 matrisen arr som en värdeparameter. Det andra anropet av F skapar automatiskt ett fyra-element int[] med de angivna elementvärdena och skickar matrisinstansen som en värdeparameter. På samma sätt skapar den tredje anropet av F ett nollelement int[] 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 med F(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 Fgäller den normala formen av F eftersom det finns en implicit konvertering från argumenttypen till parametertypen (båda är av typen object[]). Därför väljer överlagringsmatchning den normala formen av F, och argumentet skickas som en vanlig värdeparameter. I den andra och tredje anropen är den normala formen inte F tillämplig eftersom det inte finns någon implicit konvertering från argumenttypen till parametertypen (typen object kan inte konverteras implicit till typen object[]). Den utökade formen av F är dock tillämplig, så den väljs av överlagringsmatchning. Därför skapas ett ett-element object[] av anropet och det enskilda elementet i matrisen initieras med det angivna argumentvärdet (som i sig är en referens till en object[]).

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 metod N från den uppsättning metoder som deklareras i och ärvs av A. MC Detta beskrivs i §12.8.10.2.
  • Sedan vid körning:
    • Om M är en icke-virtuell metod M anropas.
    • Annars M är en virtuell metod och den mest härledda implementeringen av M med avseende R på anropas.

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 av Mden virtuella deklarationen är detta den mest härledda implementeringen av M med avseende på R.
  • Annars, om R innehåller en åsidosättning av M, är detta den mest härledda implementeringen av M med avseende på R.
  • Annars är den mest härledda implementeringen av M med avseende R på samma som den mest härledda implementeringen av M med avseende på den direkta basklassen för R.

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 metod F och en virtuell metod G. Klassen B introducerar en ny icke-virtuell metod F, vilket döljer den ärvda Foch åsidosätter även den ärvda metoden G. Exemplet genererar utdata:

A.F
B.F
B.G
B.G

Observera att -instruktionen a.G() anropar B.G, inte A.G. Det beror på att körningstypen för instansen (som är B), inte kompileringstidstypen för instansen (som är A), 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 av D och den som introducerades av AC . Metoden som introducerades med C döljer metoden som ärvts från A. Därför åsidosätter åsidosättningsdeklarationen i D metoden som introducerades av C, och det går inte D att åsidosätta metoden som introducerades av A. 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 Cbestä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() i B anropar metoden PrintFields som deklarerats i A. En base_access inaktiverar den virtuella anropsmekanismen och behandlar helt enkelt basmetoden som en icke-metodvirtual . Om anropet hade B skrivits ((A)this).PrintFields()anropar det rekursivt metoden PrintFields som deklarerats i B, inte den som deklarerats i A, eftersom PrintFields är virtuell och körningstypen ((A)this) är B.

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 i B innehåller override ingen modifierare och åsidosätter F därför inte metoden i A. F I stället döljer metoden i B metoden i A, 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 i B döljer den virtuella F metoden som ärvts från A. Eftersom den nya F i B har privat åtkomst innehåller dess omfång endast klasstexten B för och utökar inte till C. Därför tillåts indeklarationen FC att åsidosätta ärvda F från A.

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: en F metod som har sealed modifieraren och en G metod som inte gör det. B's användning av sealed modifieraren förhindrar C 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. Metoden Paint är abstrakt eftersom det inte finns någon meningsfull standardimplementering. Klasserna Ellipse och Box är konkreta Shape implementeringar. Eftersom dessa klasser inte är abstrakta måste de åsidosätta Paint 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, klassen B åsidosätter den här metoden med en abstrakt metod och klassen C å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 privatesker 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 Mgä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[], och ToInt32 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. Metoderna G och H är korrekta eftersom alla möjliga körningssökvägar slutar med en retursats som anger ett returvärde. Metoden I ä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.5readonly 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, , eller out 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 publickan den tillgänglighet som deklareras av accessor_modifier vara antingen private protected, protected internal, internal, protectedeller private.
    • Om egenskapen eller indexeraren har en deklarerad tillgänglighet för protected internalkan den tillgänglighet som deklareras av accessor_modifier vara antingen private protected, protected private, internal, protectedeller private.
    • Om egenskapen eller indexeraren har en deklarerad tillgänglighet för internal eller protectedska den tillgänglighet som deklareras av accessor_modifier vara antingen private protected eller private.
    • Om egenskapen eller indexeraren har en deklarerad tillgänglighet för private protectedska den tillgänglighet som deklareras av accessor_modifier vara private.
    • Om egenskapen eller indexeraren har en deklarerad tillgänglighet för privatefår ingen accessor_modifier användas.

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 av ref, 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 valuealltid . 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 offentlig Caption egendom. Get-accessorn för egenskapen Caption returnerar den string som lagras i det privata caption 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 ett private fält, och den angivna åtkomstgivaren ändrar fältet private 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 av Caption 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 i B döljer egenskapen P med A avseende på både läsning och skrivning. I satserna

B 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 skrivskyddade P egenskapen i B döljer egenskapen skrivskyddad P i A. Observera dock att en gjuten kan användas för att komma åt den dolda P 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 och xy, för att lagra platsen. Platsen exponeras offentligt både som en X egenskap och Y som en Location egenskap av typen Point. Om det i en framtida version av Labelblir enklare att lagra platsen internt Point 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 och y i stället varit public readonly fält hade det varit omöjligt att göra en sådan ändring i Label 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, Outoch Error, som representerar standardenheterna för indata, utdata och fel. Genom att exponera dessa medlemmar som egenskaper Console kan klassen fördröja initieringen tills de faktiskt används. När du till exempel först refererar Out till egenskapen, som i

Console.Out.WriteLine("hello, world");

den underliggande TextWriter för utdataenheten skapas. Men om programmet inte refererar till In egenskaperna och Error 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 egenskapen B.Text, även i kontexter där endast den angivna åtkomstorn anropas. Egenskapen B.Count är däremot inte tillgänglig för klassen M, så den tillgängliga egenskapen A.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 och Z är en abstrakt skrivskyddad egenskap. Eftersom Z 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, Yoch Z åsidosättande av egenskapsdeklarationer. Varje egenskapsdeklaration matchar exakt hjälpmedelsmodifierarna, typen och namnet på motsvarande ärvda egenskap. Get-accessor X för och set-accessor för Y att använda basnyckelordet för att få åtkomst till de ärvda åtkomstgivarna. Deklarationen av Z åsidosätter båda abstrakta accessorer – därför finns det inga utestående abstract funktionsmedlemmar i Boch B 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 till Click 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 nullfä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 i Button klassen. Som exemplet visar kan fältet undersökas, ändras och användas i delegerade anropsuttryck. Metoden OnClick i Button 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 ButtonClick deklaration kan medlemmen endast användas till vänster om operatorerna += och –= som i

b.Click += new EventHandler(...);

som lägger till ett ombud i anropslistan för Click händelsen, och

Click –= 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 Xgör referenser till Ev vänster om += operatorerna och –= att tilläggs- och borttagningsåtkomsterna anropas. Alla andra referenser till Ev 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. Metoden AddEventHandler associerar ett ombudsvärde med en nyckel, GetEventHandler metoden returnerar det ombud som för närvarande är associerat med en nyckel och RemoveEventHandler 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.5readonly 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 thismodifierarna , refoch 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, outeller ref 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 motsvarande bool[] (eftersom varje värde för den förra endast upptar en bit i stället för den senares en byte), men tillåter samma åtgärder som en bool[].

I följande CountPrimes klass används en BitArray 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 en bool[].

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 namnet value.
  • 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är P är egenskapsnamnet. I en övergripande indexeringsdeklaration används den ärvda indexeraren med hjälp av syntaxen base[E], där E ä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 en static 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 typen T eller T? och kan returnera vilken typ som helst.
  • En unary ++ eller -- operator ska ta en enskild parameter av typen T eller T? och ska returnera samma typ eller en typ som härleds från den.
  • En unary true eller false operator ska ta en enskild parameter av typen T eller T? och ska returnera typ bool.

Signaturen för en unary-operator består av operatortoken (+, , -!, ~, ++, --, trueeller 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 eller T?, 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 typ T eller int, och kan returnera vilken int? 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₀ och T₀ är olika typer.

  • Antingen S₀ eller T₀ ä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 till T eller från T till S.

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 respektive intstringbetraktas som unika typer utan relation. Den tredje operatorn är dock ett fel eftersom C<T> är basklassen för D<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ån C till int och från int till C, men inte från int till bool. 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ör Tdeklarerar 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 typ Tignoreras alla användardefinierade konverteringar (implicita eller explicita) från S till T .
  • Om det finns en fördefinierad explicit konvertering (§10.3) från typ S till typ Tignoreras alla användardefinierade explicita konverteringar från S till T . Dessutom:
    • Om antingen S eller T är en gränssnittstyp ignoreras användardefinierade implicita konverteringar från S till T .
    • I annat fall beaktas fortfarande användardefinierade implicita konverteringar från S till T .

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 objectdö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 till byte är implicit eftersom den aldrig utlöser undantag eller förlorar information, men konverteringen från byte till Digit är explicit eftersom Digit den bara kan representera en delmängd av möjliga värden för en byte.

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 av Bskapas följande utdata:

x = 1, y = 0

Värdet x för är 1 eftersom variabelinitieraren körs innan basklassinstanskonstruktorn anropas. Värdet y för är dock 0 (standardvärdet för en int) eftersom tilldelningen till y 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. Exemplet

class 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 och this). 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.

En static_constructor_body som är ett block eller uttryck motsvarar exakt method_bodyav en statisk metod med en returtyp (void).

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 Aden statiska konstruktorn utlöses av anropet till A.Foch körningen av Bden statiska konstruktorn utlöses av anropet till B.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ör B.Y, före klassens Bstatiska konstruktor. Y's initializer gör Aatt konstruktorn static körs eftersom värdet A.X för refereras. Den statiska konstruktorn A för i sin tur fortsätter att beräkna värdet Xför , och hämtar därmed standardvärdet Y, som är noll. A.X initieras därför till 1. Processen med att köra Ainitiatorer för statiska fält och statisk konstruktor slutförs och återgår till beräkningen av det initiala värdet för Y, 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.

En finalizer_body som är ett block eller uttryck motsvarar exakt method_bodyav en instansmetod med en returtyp (void).

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 FinalizeSystem.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.Objectmetoden .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, outeller 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 eller IEnumerable är object.
  • Avkastningstypen för en iterator som returnerar IEnumerator<T> eller IEnumerable<T> är T.

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 och IEnumerator<T>, där T ä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 MoveNextför , Currentoch 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är yield return instruktionen. Om -instruktionen yield return finns inom ett eller flera try block körs inte de associerade slutligen-blocken just nu.
    • Uppräkningsobjektets tillstånd ändras till pausat.
    • Metoden MoveNext återgår true till anroparen, vilket anger att iterationen har avancerat till nästa värde.
  • När en yield break instruktion påträffas (§9.4.4.20):
    • Om -instruktionen yield break finns inom ett eller flera try block körs de associerade finally blocken.
    • Uppräkningsobjektets tillstånd ändras till efter.
    • Metoden MoveNext återgår false till anroparen, vilket anger att iterationen är klar.
  • När slutet av iteratorns brödtext påträffas:
    • Uppräkningsobjektets tillstånd ändras till efter.
    • Metoden MoveNext återgår false 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.

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 objectmotsvarar 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 en yield 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 metodens Dispose 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 och IEnumerable<T>, där T ä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 och IEnumerator<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 till GetEnumerator. Efterföljande anrop av GetEnumerator, 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 refref structtyp.

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 typ MyTaskMethodBuilder<T> och inväntartypen Awaiter<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 internalmåste motsvarande builder-typ också deklareras internal 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 namnet builder 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() in Start() eller efter Start() att ha återvänt för att föra tillståndsmaskinen framåt.
  • När Start() den har returnerats async anropas builder.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åndsdatorn builder.SetException(e) .
  • Om tillståndsdatorn når ett await expr uttryck expr.GetAwaiter() anropas.
  • Om awaiter implementerar ICriticalNotifyCompletion och IsCompleted är falskt anropar builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine)tillståndsdatorn .
    • AwaitUnsafeOnCompleted() ska anropa awaiter.UnsafeOnCompleted(action) med ett Action som anropar stateMachine.MoveNext() när inväntaren är klar.
  • Annars anropar builder.AwaitOnCompleted(ref awaiter, ref stateMachine)tillståndsdatorn .
    • AwaitOnCompleted() ska anropa awaiter.OnCompleted(action) med ett Action som anropar stateMachine.MoveNext() när inväntaren är klar.
  • SetStateMachine(IAsyncStateMachine) kan anropas av den kompilatorgenererade implementeringen för att identifiera instansen av byggaren som är associerad IAsyncStateMachine med en tillståndsdatorinstans, särskilt i fall där tillståndsdatorn implementeras som en värdetyp.
    • Om byggaren anropar stateMachine.SetStateMachine(stateMachine)stateMachine anropas builder.SetStateMachine(stateMachine) den builder-instans som är associerad medstateMachine.

Obs! För både SetResult(T result) och «TaskType»<T> Task { get; }måste parametern respektive argumentet vara identitets cabriolet till T. 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 OperationCanceledExceptionregistreras 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 voidskiljer 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.