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 ett semikolon.

En klassdeklaration får inte tillhandahålla type_parameter_constraints_clause om den inte också tillhandahåller en type_parameter_list.

En klassdeklaration som tillhandahåller en type_parameter_list är en allmän klassdeklaration. Dessutom är alla klasser kapslade i en generisk klassdeklaration eller en generisk structdeklaration i sig en generisk klassdeklaration, eftersom typargument för den innehållande typen ska tillhandahållas för att skapa en konstruerad typ (§8.4).

15.2.2 Klassmodifierare

15.2.2.1 Allmänt

En class_declaration kan eventuellt innehålla en sekvens med klassmodifierare:

class_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'abstract'
    | 'sealed'
    | 'static'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).

Det är ett kompileringsfel för samma modifierare som ska visas flera gånger i en klassdeklaration.

Modifieraren new tillåts i kapslade klasser. Den anger att klassen döljer en ärvd medlem med samma namn, enligt beskrivningen i §15.3.5. Det är ett kompileringsfel om new-modifieraren visas i en klassdeklaration som inte är en nästad klassdeklaration.

Modifierarna public, protected, 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, sealed och static diskuteras i följande delavsnitt.

15.2.2.2 Abstrakta klasser

Modifieraren abstract används för att indikera att en klass är ofullständig och att den endast är avsedd att användas som basklass. En abstrakt klass skiljer sig från en icke-abstrakt klass på följande sätt:

  • Det går inte att instansiera en abstrakt klass direkt, och det är ett kompileringsfel att använda operatorn i new en abstrakt klass. Även om det är möjligt att ha variabler och värden vars kompileringstidstyper är abstrakta, är sådana variabler och värden nödvändigtvis 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 konkret.

15.2.2.3 Förseglade klasser

Modifieraren sealed används för att förhindra härledning från en klass. Ett kompileringsfel uppstår om en förseglad klass anges som basklass för en annan klass.

En förseglad klass kan inte heller vara en abstrakt klass.

Obs! Modifieraren sealed används främst för att förhindra oavsiktlig härledning, men den möjliggör även vissa körningsoptimeringar. Eftersom en förseglad klass är känd för att aldrig ha några härledda klasser är det särskilt möjligt att omvandla anrop för virtuella funktionsmedlemmar på förseglade klassinstanser till icke-virtuella anrop. slutkommentar

Om en eller flera delar av en partiell typdeklaration (§15.2.7) av en klass inkluderar sealed modifieraren, är klassen förseglad. Annars är klassen oförseglad.

15.2.2.4 Statiska klasser

15.2.2.4.1 Allmänt

Modifieraren static används för att markera klassen som deklareras som en statisk klass. En statisk klass får inte instansieras, ska inte användas som en typ och får endast innehålla statiska medlemmar. Endast en statisk klass kan innehålla deklarationer av tilläggsmetoder (§15.6.10).

En statisk klassdeklaration omfattas av följande begränsningar:

  • En statisk klass får inte innehålla en sealed 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-namn är T i en typeof_expression (§12.8.18) av formen typeof(T).

En primary_expression (§12.8) är tillåten att referera till en statisk klass om

  • Den primary_expression är E i en member_access (§12.8.7) av formen 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
    : '<' decorated_type_parameter (',' decorated_type_parameter)* '>'
    ;

decorated_type_parameter
    : attributes? type_parameter
    ;

type_parameter definieras i §8.5.

Varje typparameter i en klassdeklaration definierar ett namn i deklarationsutrymmet (§7.3) för den klassen. Därför kan den inte ha samma namn som en annan typparameter för den klassen eller en medlem som deklarerats i den klassen. En typparameter får inte ha samma namn som själva typen.

Två partiella allmänna typdeklarationer (i samma program) bidrar till samma obundna generiska typ om de har samma fullständigt kvalificerade namn (som innehåller en generic_dimension_specifier (§12.8.18) för antalet typparametrar) (§7.8.3). Två sådana partiella typdeklarationer ska ange samma namn för varje typparameter i ordning.

15.2.4 Klassbasspecifikation

15.2.4.1 Allmänt

En klassdeklaration kan innehålla en class_base specifikation, som definierar klassens direkta basklass och gränssnitten (§18) som implementeras direkt av klassen.

class_base
    : ':' class_type
    | ':' interface_type_list
    | ':' class_type ',' interface_type_list
    ;

interface_type_list
    : interface_type (',' interface_type)*
    ;

15.2.4.2 Basklasser

När en class_type ingår i class_base anger den den direkta basklassen för klassen som deklareras. Om en icke-partiell klassdeklaration inte har någon class_base, eller om class_base endast visar gränssnittstyper, antas den direkta basklassen vara object. När en partiell klassdeklaration innehåller en basklassspecifikation ska basklassspecifikationen referera till samma typ som alla andra delar av den partiella typen som innehåller en basklassspecifikation. Om ingen del av en partiell klass innehåller en basklassspecifikation är 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 basklasspecifikationen X<Z.Y> anser att den direkta basklassen för Z är object, och därför (enligt reglerna i §7.8) anses Z inte ha en medlem Y.

slutexempel

Basklasserna för en klass är den direkta basklassen och dess basklasser. Med andra ord är uppsättningen basklasser den transitiva avslutningen av den direkta basklassrelationen.

Exempel: I följande:

class A {...}
class B<T> : A {...}
class C<T> : B<IComparable<T>> {...}
class D<T> : C<T[]> {...}

basklasserna D<int> för ä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 mängden klasser som en viss klass är beroende av den transitiva slutningen av direkt beroende av-relationen.

Exempel: Exemplet

class A : A {}

är felaktig eftersom klassen är beroende av sig själv. Likaså exemplet

class A : B {}
class B : C {}
class C : A {}

är fel eftersom klasserna är cirkulärt beroende av sig själva. Slutligen exemplet

class A : B.C {}
class B : A
{
    public class C {}
}

Resulterar i ett kompileringsfel eftersom A är beroende av B.C (dess direkta basklass), som i sin tur beror på B (dess omedelbart omslutande klass), och denna klass cirkulärt beror på A.

slutexempel

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

Exempel: I följande kod

class A
{
    class B : A {}
}

B beror på A (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 är inte möjligt att härleda från en sluten klass.

Exempel: I följande kod

sealed class A {}
class B : A {} // Error, cannot derive from a sealed class

Klassen B har ett fel eftersom den försöker härleda från den förseglade klassen A.

slutexempel

15.2.4.3 Implementeringar av gränssnitt

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

Gränssnittsuppsättningen för en typ som är deklarerad i flera delar (§15.2.7) är unionen av de gränssnitt som anges på varje del. En viss gränssnitt kan bara namnges en gång på varje del, men flera delar kan namnge samma basgränssnitt. Det får bara finnas en implementering av varje medlem i ett visst gränssnitt.

Exempel: I följande:

partial class C : IA, IB {...}
partial class C : IC {...}
partial class C : IA, IB {...}

uppsättningen med basgränssnitt för klassen C är IA, IBoch IC.

slutexempel

Vanligtvis tillhandahåller varje del en implementering av de gränssnitt som deklareras på den delen; dock är detta inte ett krav. En del kan tillhandahålla implementeringen för ett gränssnitt som deklarerats på en annan del.

Exempel:

partial class X
{
    int IComparable.CompareTo(object o) {...}
}

partial class X : IComparable
{
    ...
}

slutexempel

De basgränssnitt som anges i en klassdeklaration kan vara konstruerade gränssnittstyper (§8.4, §18.2). Ett basgränssnitt kan inte vara en typparameter på egen hand, även om det kan omfatta de typparametrar som finns i omfånget.

Exempel: Följande kod visar hur en klass kan implementera och utöka konstruerade typer:

class C<U, V> {}
interface I1<V> {}
class D : C<string, int>, I1<string> {}
class E<T> : C<int, T>, I1<T> {}

slutexempel

Gränssnittsimplementeringar diskuteras ytterligare i §18.6.

15.2.5 Typparameterbegränsningar

Allmänna typ- och metoddeklarationer kan också ange typparameterbegränsningar genom att inkludera type_parameter_constraints_clauses.

type_parameter_constraints_clause
    : 'where' type_parameter ':' type_parameter_constraints
    ;

type_parameter_constraints
    : primary_constraint (',' secondary_constraints)? (',' constructor_constraint)?
    | secondary_constraints (',' constructor_constraint)?
    | constructor_constraint
    ;

primary_constraint
    : class_type nullable_type_annotation?
    | 'class' nullable_type_annotation?
    | 'struct'
    | 'notnull'
    | 'unmanaged'
    ;

secondary_constraint
    : interface_type nullable_type_annotation?
    | type_parameter nullable_type_annotation?
    ;

secondary_constraints
    : secondary_constraint (',' secondary_constraint)*
    ;

constructor_constraint
    : 'new' '(' ')'
    ;

Varje type_parameter_constraints_clause består av token where, följt av namnet på en typparameter, följt av ett kolon och en lista med begränsningar för den typparametern. Det kan finnas högst en where sats för varje typparameter och satserna where kan anges i valfri ordning. Precis som get- och set-tokens i en egenskapsåtkomst är where-token inte ett nyckelord.

Listan över begränsningar som anges i en where -sats kan innehålla någon av följande komponenter i den här ordningen: en enda primär begränsning, en eller flera sekundära begränsningar och konstruktorvillkoret. new()

En primär begränsning kan vara en klasstyp, referenstypsbegränsningenclass, värdetypsbegränsningenstruct, icke-null-begränsningennotnull eller begränsningen för ohanterad typunmanaged. Klasstypen och referenstypbegränsningen kan innehålla nullable_type_annotation.

En sekundär begränsning kan vara en interface_type eller type_parameter, eventuellt följt av en nullable_type_annotation. Förekomsten av nullable_type_annotation anger att typargumentet tillåts vara den nullbara referenstypen som motsvarar en referenstyp som inte är nullbar som uppfyller villkoret.

Begränsningen för referenstyp anger att ett typargument som används för typparametern ska vara en referenstyp. Alla klasstyper, gränssnittstyper, ombudstyper, matristyper och typparametrar som är kända för att vara en referenstyp (enligt definitionen nedan) uppfyller den här begränsningen.

Klasstypen, referenstypsbegränsningen och de sekundära begränsningarna kan innehålla nullable-typsanteckningen. Förekomsten eller frånvaron av den här kommentaren på typparametern anger förväntningarna på nullabilitet för typargumentet:

  • Om villkoret inte innehåller den nullbara typanteckningen förväntas typargumentet vara en referenstyp som inte är nullbar. En kompilator kan utfärda en varning om typargumentet är en referenstyp som kan ogiltigförklaras.
  • Om villkoret innehåller den nullbara typanteckningen, uppfylls villkoret av både en referenstyp som inte kan vara null och en referenstyp som kan vara null.

Ogiltigheten för typargumentet behöver inte matcha typparameterns nullabilitet. En kompilator kan utfärda en varning om nullbarheten för typparametern inte matchar typargumentets nullbarhet.

Obs! Om du vill ange att ett typargument är en nullbar referenstyp lägger du inte till anteckningen av typen null som en begränsning (användning T : class 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 nulllbara typanteckningen, ?, kan inte användas på ett obundet typargument.

För en typparameter T när typargumentet är en nullbar referenstyp C?tolkas instanser av T? som C?, inte C??.

Exempel: Följande exempel visar hur nullbarheten för ett typargument påverkar nullbarheten för en deklaration av dess typparameter:

public class C
{
}

public static class  Extensions
{
    public static void M<T>(this T? arg) where T : notnull
    {

    }
}

public class Test
{
    public void M()
    {
        C? mightBeNull = new C();
        C notNull = new C();

        int number = 5;
        int? missing = null;

        mightBeNull.M(); // arg is C?
        notNull.M(); //  arg is C?
        number.M(); // arg is int?
        missing.M(); // arg is int?
    }
}

När typargumentet är en icke-nullbar typ ? anger typanteckningen att parametern är motsvarande null-typ. När typargumentet redan är en nullbar referenstyp är parametern samma null-typ.

slutexempel

Villkoret inte null anger att ett typargument som används för typparametern ska vara en icke-nullbar värdetyp eller en referenstyp som inte kan noll. Ett typargument som inte är en icke-nullbar värdetyp eller en icke-nullbar referenstyp tillåts, men en kompilator kan generera en diagnostikvarning.

Eftersom notnull inte är ett nyckelord i primary_constraint är inte null-villkoret alltid syntaktiskt tvetydigt med class_type. Om ett namnuppslag (§12.8.4) av namnet notnull lyckas ska det av kompatibilitetsskäl behandlas som en class_type. Annars ska den behandlas som NOT NULL-restriktionen.

Exempel: Följande klass visar användningen av olika typer av argument mot olika begränsningar, vilket anger varningar som kan utfärdas av en kompilator.

#nullable enable
public class C { }
public class A<T> where T : notnull { }
public class B1<T> where T : C { }
public class B2<T> where T : C? { }
class Test
{
    static void M()
    {
        // nonnull constraint allows nonnullable struct type argument
        A<int> x1;
        // possible warning: nonnull constraint prohibits nullable struct type argument
        A<int?> x2;
        // nonnull constraint allows nonnullable class type argument
        A<C> x3;
        // possible warning: nonnull constraint prohibits nullable class type argument
        A<C?> x4;
        // nonnullable base class requirement allows nonnullable class type argument
        B1<C> x5;
        // possible warning: nonnullable base class requirement prohibits nullable class type argument
        B1<C?> x6;
        // nullable base class requirement allows nonnullable class type argument
        B2<C> x7;
        // nullable base class requirement allows nullable class type argument
        B2<C?> x8;
    }
}

Begränsningen för värdetyp anger att ett typargument som används för typparametern ska vara en värdetyp som inte kan nulleras. Alla icke-nullbara struct-typer, uppräkningstyp och typparametrar med begränsningen för värdetyper uppfyller den här begränsningen. Observera att även om den klassificeras som en värdetyp uppfyller inte en nullbar värdetyp (§8.3.12) värdetypsbegränsningen. En typparameter med villkoret värdetyp får inte heller ha constructor_constraint, även om den kan användas som typargument för en annan typparameter med en constructor_constraint.

Obs! Typen System.Nullable<T> anger den icke-nullbara värdetypsbegränsningen för T. Således är rekursivt konstruerade typer av formulär T?? och Nullable<Nullable<T>> förbjudna. slutkommentar

Villkoret ohanterad typ anger att ett typargument som används för typparametern ska vara en icke-nullbar ohanterad typ (§8.8).

Eftersom unmanaged inte är ett nyckelord i primary_constraint är den ohanterade begränsningen alltid syntaktiskt tvetydig med class_type. Av kompatibilitetsskäl, om ett namnuppslag (§12.8.4) av namnet unmanaged lyckas behandlas det som en class_type. Annars behandlas det som begränsningen utan hantering.

Pekartyper tillåts aldrig vara typargument och uppfyller inte några typbegränsningar, inte ens ohanterade, trots att de är ohanterade typer.

Om en begränsning är en klasstyp, en gränssnittstyp eller en typparameter anger den typen en minimal "bastyp" som varje typargument som används för den typparametern ska stödja. När en konstruerad typ eller generisk metod används kontrolleras typargumentet mot begränsningarna för typparametern vid kompileringstid. Det angivna typargumentet skall uppfylla de villkor som beskrivs i §8.4.5.

Ett class_type villkor ska uppfylla följande regler:

  • Typen ska vara en klasstyp.
  • Typen får inte vara sealed.
  • Typen får inte vara någon av följande typer: System.Array 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 att S har begränsningen för värdetyper och att T har begränsningen för referenstyper. I praktiken begränsar T detta till typerna System.Object, System.ValueType, System.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 och att också ha en constructor_constraint.

Exempel: Följande är exempel på begränsningar:

interface IPrintable
{
    void Print();
}

interface IComparable<T>
{
    int CompareTo(T value);
}

interface IKeyProvider<T>
{
    T GetKey();
}

class Printer<T> where T : IPrintable {...}
class SortedList<T> where T : IComparable<T> {...}

class Dictionary<K,V>
    where K : IComparable<K>
    where V : IPrintable, IKeyProvider<K>, new()
{
    ...
}

Följande exempel är felaktigt eftersom det orsakar en cirkulärhet i beroendediagrammet för typparametrarna:

class Circular<S,T>
    where S: T
    where T: S // Error, circularity in dependency graph
{
    ...
}

Följande exempel illustrerar ytterligare ogiltiga situationer:

class Sealed<S,T>
    where S : T
    where T : struct // Error, `T` is sealed
{
    ...
}

class A {...}
class B {...}

class Incompat<S,T>
    where S : A, T
    where T : B // Error, incompatible class-type constraints
{
    ...
}

class StructWithClass<S,T,U>
    where S : struct, T
    where T : U
    where U : A // Error, A incompatible with struct
{
    ...
}

slutexempel

Den dynamiska raderingen av en typ C är en typ Cₓ konstruerad enligt följande:

  • Om C är en kapslad typ, så är Outer.Inner en kapslad typ Cₓ.
  • 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 arraytyp E[] så är Cₓ arraytypen Eₓ[].
  • Om C är dynamiskt är det Cₓobject.
  • Annars är CₓC.

Den effektiva basklassen för en typparameter T definieras på följande sätt:

Låt R vara en uppsättning typer sådana att:

  • För varje begränsning av T som är en typparameter, innehåller R dess effektiva basklass.
  • För varje begränsning av T som är en structtyp, innehåller RSystem.ValueType.
  • För varje begränsning av T som är en uppräkningstyp, R innehåller System.Enum.
  • För varje begränsning av T som är en delegattype, innehåller R dess dynamiska radering.
  • För varje begränsning av typen matris T innehåller RSystem.Array.
  • För varje begränsning i T som är av typen klass, innehåller R dess dynamiska radering.

  • Om T har begränsningen för värdetyp är System.ValueTypedess effektiva basklass .
  • Om R är tom, då är den effektiva basklassen object.
  • 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 den effektiva basklassen för Tobject. Konsekvensreglerna säkerställer att den mest omfattande typen finns.

Om typparametern är en parameter av metodtyp vars begränsningar ärvs från basmetoden beräknas den effektiva basklassen efter typersättning.

Dessa regler säkerställer att den effektiva basklassen alltid är en class_type.

Den effektiva gränssnittsuppsättningen för en typparameter T definieras på följande sätt:

  • Om T saknar secondary_constraints, är dess effektiva gränssnittsuppsättning tom.
  • Om T har interface_type-begränsningar men inga type_parameter-begränsningar, är dess effektiva gränssnittsuppsättning uppsättningen av dynamiska borttagningar av dess interface_type-begränsningar.
  • Om T det inte finns några interface_type begränsningar men har type_parameter begränsningar är dess effektiva gränssnittsuppsättning en union av de effektiva gränssnittsuppsättningarna för dess type_parameter begränsningar.
  • Om T har både interface_type begränsningar och type_parameter begränsningar är dess effektiva gränssnittsuppsättning en union av uppsättningen dynamiska radering av dess interface_type begränsningar och de effektiva gränssnittsuppsättningarna för dess type_parameter begränsningar.

En typparameter är känd för att vara en referenstyp om den har referenstypsbegränsningen eller om dess effektiva basklass inte object är eller System.ValueType. En typparameter är känd för att vara en icke-nullbar referenstyp om den är känd för att vara en referenstyp och har den icke-nullbara referenstypsbegränsningen.

Värden av en begränsad typparametertyp kan användas för att komma åt de instansmedlemmar som är underförstådda av begränsningarna.

Exempel: I följande:

interface IPrintable
{
    void Print();
}

class Printer<T> where T : IPrintable
{
    void PrintOne(T x) => x.Print();
}

IPrintable-metoderna kan anropas direkt på x eftersom T alltid måste 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 Klasskropp

Class_body för en klass definierar medlemmarna i den klassen.

class_body
    : '{' class_member_declaration* '}'
    ;

15.2.7 Partiella typdeklarationer

Modifieraren partial används när du definierar en klass, struct eller gränssnittstyp i flera delar. Den partial modifieraren är ett kontextuellt nyckelord (§6.4.4) och har särskild betydelse omedelbart före nyckelorden class, structoch interface. (En partiell typ kan innehålla partiella metoddeklarationer (§15.6.9).

Varje del av en partiell typdeklaration ska innehålla en partial modifierare och deklareras i samma namnområde eller innehålla typ som de andra delarna. Modifieraren partial anger att ytterligare delar av typdeklarationen kan finnas någon annanstans, men förekomsten av sådana ytterligare delar är inte ett krav. Det är giltigt för den enda deklarationen av en typ som innehåller partial modifieraren. Det är giltigt för endast en deklaration av en partiell typ att inkludera basklassen eller implementerade gränssnitt. Alla deklarationer av en basklass eller implementerade gränssnitt ska dock matcha, inklusive ogiltigheten för alla angivna typargument.

Alla delar av en partiell typ ska sammanställas så att delarna kan sammanfogas vid kompileringstillfället. Partiella typer tillåter inte att redan kompilerade typer utökas.

Kapslade typer kan deklareras i flera delar med hjälp av partial modifieraren. Vanligtvis deklareras även den innehållande typen med partial och varje del av den kapslade typen deklareras i en annan del av den innehållande typen.

Exempel: Följande partiella klass implementeras i två delar, som finns i olika kompileringsenheter. Den första delen är maskinellt genererad av ett databasmappningsverktyg medan den andra delen är skapad manuellt.

public partial class Customer
{
    private int id;
    private string name;
    private string address;
    private List<Order> orders;

    public Customer()
    {
        ...
    }
}

// File: Customer2.cs
public partial class Customer
{
    public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted);

    public bool HasOutstandingOrders() => orders.Count > 0;
}

När de två delarna ovan kompileras tillsammans fungerar den resulterande koden som om klassen hade skrivits som en enda enhet, enligt följande:

public class Customer
{
    private int id;
    private string name;
    private string address;
    private List<Order> orders;

    public Customer()
    {
        ...
    }

    public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted);

    public bool HasOutstandingOrders() => orders.Count > 0;
}

slutexempel

Hanteringen av attribut som anges på typ- eller typparametrarna för olika delar av en partiell typdeklaration beskrivs i §22.3.

15.3 Klassmedlemmar

15.3.1 Allmänt

Medlemmarna i en klass består av de medlemmar som introduceras av dess class_member_declarationoch de medlemmar som ärvts från den direkta basklassen.

class_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | operator_declaration
    | constructor_declaration
    | finalizer_declaration
    | static_constructor_declaration
    | type_declaration
    ;

Medlemmarna i en klass är indelade i följande kategorier:

  • Konstanter, som representerar konstanta värden som är associerade med klassen (§15.4).
  • Fält, som är variablerna i klassen (§15.5).
  • Metoder som implementerar de beräkningar och åtgärder som kan utföras av klassen (§15.6).
  • Egenskaper, som definierar namngivna egenskaper och de åtgärder som är associerade med att läsa och skriva dessa egenskaper (§15.7).
  • Händelser som definierar meddelanden som kan genereras av klassen (§15.8).
  • Indexerare, som tillåter att instanser av klassen indexeras på samma sätt (syntaktiskt) som matriser (§15.9).
  • Operatorer som definierar de uttrycksoperatorer som kan tillämpas på instanser av klassen (§15.10).
  • Instanskonstruktorer som implementerar de åtgärder som krävs för att initiera instanser av klassen (§15.11)
  • Slutförare, som implementerar de åtgärder som ska utföras innan instanser av klassen tas bort permanent (§15.13).
  • Statiska konstruktorer som implementerar de åtgärder som krävs för att initiera själva klassen (§15.12).
  • Typer, som representerar de typer som är lokala för klassen (§14.7).

En class_declaration skapar ett nytt deklarationsutrymme (§7.3) och typ-parametrarna samt de class_member_declaration som omedelbart ingår i class_declaration introducerar nya medlemmar i detta deklarationsutrymme. Följande regler gäller för class_member_declarations:

  • Instanskonstruktorer, finalizers och statiska konstruktorer ska ha samma namn som den omedelbart omslutande klassen. Alla andra medlemmar ska ha namn som skiljer sig från namnet på den omedelbart omslutande klassen.

  • Namnet på en typparameter i type_parameter_list för en klassdeklaration ska skilja sig från namnen på alla andra typparametrar i samma type_parameter_list och ska skilja sig från namnet på klassen och namnen på alla medlemmar i klassen.

  • Namnet på en typ ska skilja sig från namnen på alla icke-typmedlemmar som deklarerats i samma klass. Om två eller flera typdeklarationer har samma fullständigt kvalificerade namn ska deklarationerna ha partial modifieraren (§15.2.7) och dessa deklarationer kombineras för att definiera en enda typ.

Obs! Eftersom det fullständigt kvalificerade namnet på en typdeklaration kodar antalet typparametrar kan två olika typer dela samma namn så länge de har olika antal typparametrar. slutkommentar

  • Namnet på en konstant, ett fält, en egenskap eller en händelse ska skilja sig från namnen på alla andra medlemmar som deklarerats i samma klass.

  • Namnet på en metod ska skilja sig från namnen på alla andra icke-metoder som deklareras i samma klass. Dessutom ska signaturen (§7.6) för en metod skilja sig från signaturerna för alla andra metoder som deklarerats i samma klass, och två metoder som deklarerats i samma klass får inte ha signaturer som skiljer sig enbart 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

Medlemsuppsättningen för en typ som deklareras i flera delar (§15.2.7) utgör unionen av medlemmarna som deklareras i varje del. Organen i alla delar av typdeklarationen delar samma deklarationsutrymme (§7.3), och varje medlems omfattning (§7.7) sträcker sig till alla delars organ. Tillgänglighetsdomänen för alla medlemmar innehåller alltid alla delar av den omslutande typen, en privat medlem som deklareras i en del är fritt tillgänglig från en annan del. Det är ett kompileringsfel att deklarera samma medlem i mer än en del av typen, såvida inte medlemmen har partial-modifieraren.

Exempel:

partial class A
{
    int x;                   // Error, cannot declare x more than once
    partial void M();        // Ok, defining partial method declaration

    partial class Inner      // Ok, Inner is a partial type
    {
        int y;
    }
}

partial class A
{
    int x;                   // Error, cannot declare x more than once
    partial void M() { }     // Ok, implementing partial method declaration

    partial class Inner      // Ok, Inner is a partial type
    {
        int z;
    }
}

slutexempel

Fältinitieringsordningen kan vara betydande inom C#-koden och vissa garantier ges enligt definitionen i §15.5.6.1. I annat fall är ordningen på medlemmar inom en typ sällan betydande, men kan vara betydande när de samverkar med andra språk och miljöer. I dessa fall är ordningen på medlemmar inom en typ som deklarerats i flera delar odefinierad.

15.3.2 Instanstypen

Varje klassdeklaration har en associerad instanstyp. För en allmän klassdeklaration bildas instanstypen genom att skapa en konstruerad typ (§8.4) från typdeklarationen, där var och en av de angivna typargumenten är motsvarande typparameter. Eftersom instanstypen använder typparametrarna kan den bara användas där typparametrarna finns i omfånget. det vill: i klassdeklarationen. Instanstypen är typen av this för kod som skrivits i klassdeklarationen. För icke-generiska klasser är instanstypen helt enkelt den deklarerade klassen.

Exempel: Följande visar flera klassdeklarationer tillsammans med deras instanstyper:

class A<T>             // instance type: A<T>
{
    class B {}         // instance type: A<T>.B
    class C<U> {}      // instance type: A<T>.C<U>
}
class D {}             // instance type: D

slutexempel

15.3.3 Medlemmar av konstruerade typer

De icke-ärvda medlemmarna av en konstruktionstyp erhålls genom att, för varje type_parameter i medlemsdeklarationen, ersätta motsvarande type_argument av den konstruerade typen. Ersättningsprocessen baseras på den semantiska innebörden av typdeklarationer och är inte bara textersättning.

Exempel: Med den generiska klassdeklarationen

class Gen<T,U>
{
    public T[,] a;
    public void G(int i, T t, Gen<U,T> gt) {...}
    public U Prop { get {...} set {...} }
    public int H(double d) {...}
}

den konstruerade typen Gen<int[],IComparable<string>> har följande medlemmar:

public int[,][] a;
public void G(int i, int[] t, Gen<IComparable<string>,int[]> gt) {...}
public IComparable<string> Prop { get {...} set {...} }
public int H(double d) {...}

Typen av medlem a i den generiska 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

Inom instansfunktionens medlemmar är typen av this instanstypen (§15.3.2) av den innefattande deklarationen.

Alla medlemmar i en generisk klass kan använda typparametrar från valfri omslutande klass, antingen direkt eller som en del av en konstruerad typ. När en viss sluten konstruktionstyp (§8.4.3) används vid körning ersätts varje användning av en typparameter med typargumentet som levereras till den konstruerade typen.

Exempel:

class C<V>
{
    public V f1;
    public C<V> f2;

    public C(V x)
    {
        this.f1 = x;
        this.f2 = this;
    }
}

class Application
{
    static void Main()
    {
        C<int> x1 = new C<int>(1);
        Console.WriteLine(x1.f1);              // Prints 1

        C<double> x2 = new C<double>(3.1415);
        Console.WriteLine(x2.f1);              // Prints 3.1415
    }
}

slutexempel

15.3.4 Arv

En klass ärver medlemmarna i dess direkta basklass. Arv innebär att en klass implicit innehåller alla medlemmar i dess direkta basklass, förutom instanskonstruktorer, finalizers och statiska konstruktorer för basklassen. Några viktiga aspekter av arv är:

  • Arv är transitivt. Om C härleds frå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 byta ut i medlemsdeklarationen för varje type_parameter mot det motsvarande type_argument från base_class_specification.

Exempel:

class B<U>
{
    public U F(long index) {...}
}

class D<T> : B<T[]>
{
    public T G(string s) {...}
}

I koden ovan har den konstruerade typen D<int> en icke-ärvd offentlig medlem intG(string s), erhållen genom att ersätta typargumentet int med 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[]> av D<int> genom att ersätta int med T 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. En medlems konstituerande typer ska vara minst lika tillgängliga som medlemmen själv (§7.5.5).

15.3.8 Statiska medlemmar och instansmedlemmar

Medlemmar i en klass är antingen statiska medlemmar eller instansmedlemmar.

Obs! Generellt sett är det bra att tänka på statiska medlemmar som tillhör klasser och instansmedlemmar som tillhör objekt (instanser av klasser). slutkommentar

När en fält-, metod-, egenskaps-, händelse-, operator- eller konstruktordeklaration innehåller en static modifierare deklareras en statisk medlem. Dessutom deklarerar en konstant- eller typdeklaration implicit en statisk medlem. Statiska medlemmar har följande egenskaper:

  • När en statisk medlem M refereras i en member_access (§12.8.7) i formuläret E.Mska E en typ som har en medlem Manges . Det är ett kompileringsfel att beteckna en instans med E.
  • 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 beteckna en typ.
  • Varje instans av en klass innehåller en separat uppsättning av alla instansfält i klassen.
  • En instansfunktionsmedlem (metod, egenskap, indexerare, instanskonstruktor eller finalizer) fungerar på en viss instans av klassen, och den här instansen kan nås som this (§12.8.14).

Exempel: I följande exempel visas reglerna för åtkomst till statiska medlemmar och instansmedlemmar:

class Test
{
    int x;
    static int y;
    void F()
    {
        x = 1;               // Ok, same as this.x = 1
        y = 1;               // Ok, same as Test.y = 1
    }

    static void G()
    {
        x = 1;               // Error, cannot access this.x
        y = 1;               // Ok, same as Test.y = 1
    }

    static void Main()
    {
        Test t = new Test();
        t.x = 1;       // Ok
        t.y = 1;       // Error, cannot access static member through instance
        Test.x = 1;    // Error, cannot access instance member through type
        Test.y = 1;    // Ok
    }
}

Metoden F visar att i en instansfunktionsmedlem kan en simple_name (§12.8.4) användas för att komma åt både instansmedlemmar och statiska medlemmar. 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 struktur kallas en kapslad typ. En typ som deklareras i en kompileringsenhet eller ett namnområde kallas för en icke-kapslad typ.

Exempel: I följande exempel:

class A
{
    class B
    {
        static void F()
        {
            Console.WriteLine("A.B.F");
        }
    }
}

klass B är en kapslad typ eftersom den deklareras i 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å den typdeklaration där typ N deklareras och N är det okvalificerade namnet (§7.8.2) av den kapslade typdeklarationen (inklusive alla generic_dimension_specifier (§12.8.18)).

15.3.9.3 Deklarerad tillgänglighet

Ej kapslade typer kan ha public eller internal deklarerad tillgänglighet och har internal deklarerad tillgänglighet som standard. Kapslade typer kan också ha dessa former av deklarerad tillgänglighet, plus en eller flera ytterligare former av deklarerad tillgänglighet, beroende på om den innehållande typen är en klass eller struct:

  • En kapslad typ som deklareras i en klass kan ha någon av de tillåtna nivåerna av deklarerad tillgänglighet och har, i likhet med andra klassmedlemmar, som standard samma deklarerade tillgänglighet som private.
  • En nästlad typ som deklareras i en strukt kan ha någon av tre former av deklarerad åtkomst (public, internal, eller private) och, precis som andra struktmedlemmar, har som standard private deklarerad åtkomst.

Exempel: Exemplet

public class List
{
    // Private data structure
    private class Node
    {
        public object Data;
        public Node? Next;

        public Node(object data, Node? next)
        {
            this.Data = data;
            this.Next = next;
        }
    }

    private Node? first = null;
    private Node? last = null;

    // Public interface
    public void AddToFront(object o) {...}
    public void AddToBack(object o) {...}
    public object RemoveFromFront() {...}
    public object RemoveFromBack() {...}
    public int Count { get {...} }
}

deklarerar en privat inkapslad klass Node.

slutexempel

15.3.9.4 Döljer

En inbäddad typ kan dölja (§7.7.2.2) en basmedlem. Modifieraren new (§15.3.5) är tillåten på kapslade typdeklarationer så att döljande kan uttryckas explicit.

Exempel: Exemplet

class Base
{
    public static void M()
    {
        Console.WriteLine("Base.M");
    }
}

class Derived: Base
{
    public new class M
    {
        public static void F()
        {
            Console.WriteLine("Derived.M.F");
        }
    }
}

class Test
{
    static void Main()
    {
        Derived.M.F();
    }
}

visar en kapslad klass M som döljer metoden M som definierats i Base.

slutexempel

15.3.9.5 denna åtkomst

En kapslad typ och dess omgivande typ har ingen särskild relation gällande this_access (§12.8.14). Mer specifikt kan this inom en kapslad typ inte användas för att referera till instansmedlemmar av den innehållande typen. I de fall där en kapslad typ behöver åtkomst till instansmedlemmarna av dess innehållande typ kan åtkomst tillhandahållas genom att ange this för instansen av den innehållande typen som ett konstruktorargument för den kapslade typen.

Exempel: Följande exempel

class C
{
    int i = 123;
    public void F()
    {
        Nested n = new Nested(this);
        n.G();
    }

    public class Nested
    {
        C this_c;

        public Nested(C c)
        {
            this_c = c;
        }

        public void G()
        {
            Console.WriteLine(this_c.i);
        }
    }
}

class Test
{
    static void Main()
    {
        C c = new C();
        c.F();
    }
}

visar den här tekniken. En instans av C skapar en instans 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 deklarerade åtkomstnivåer.

Exempel: Exemplet

class C
{
    private static void F() => Console.WriteLine("C.F");

    public class Nested
    {
        public static void G() => F();
    }
}

class Test
{
    static void Main() => C.Nested.G();
}

visar en klass C som innehåller en kapslad 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 endast skrivbar.

Exempel: I följande kod

class A
{
    public int P
    {
        get => 123;
    }
}

class B : A
{
    public new int get_P() => 456;

    public new void set_P(int value)
    {
    }
}

class Test
{
    static void Main()
    {
        B b = new B();
        A a = b;
        Console.WriteLine(a.P);
        Console.WriteLine(b.P);
        Console.WriteLine(b.get_P());
    }
}

En klass A definierar en skrivskyddad 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 skrivbar.

Dessutom är medlemsnamnet Item reserverat.

15.3.10.5 Medlemsnamn reserverade för finalizers

För en klass som innehåller en finalator (§15.13) är följande signatur reserverad:

void Finalize();

15.3.10.6 Metodnamn reserverade för operatorer

Följande metodnamn är reserverade. Även om många har motsvarande operatorer i den här specifikationen är vissa reserverade för användning av framtida versioner, medan vissa är reserverade för interop med andra språk.

Metodnamn C#-operator
op_Addition + (binärt)
op_AdditionAssignment (reserverad)
op_AddressOf (reserverad)
op_Assign (reserverad)
op_BitwiseAnd & (binärt)
op_BitwiseAndAssignment (reserverad)
op_BitwiseOr \|
op_BitwiseOrAssignment (reserverad)
op_CheckedAddition (reserverad för framtida användning)
op_CheckedDecrement (reserverad för framtida användning)
op_CheckedDivision (reserverad för framtida användning)
op_CheckedExplicit (reserverad för framtida användning)
op_CheckedIncrement (reserverad för framtida användning)
op_CheckedMultiply (reserverad för framtida användning)
op_CheckedSubtraction (reserverad för framtida användning)
op_CheckedUnaryNegation (reserverad för framtida användning)
op_Comma (reserverad)
op_Decrement -- (prefix och postfix)
op_Division /
op_DivisionAssignment (reserverad)
op_Equality ==
op_ExclusiveOr ^
op_ExclusiveOrAssignment (reserverad)
op_Explicit explicit (inskränkande) tvång
op_False false
op_GreaterThan >
op_GreaterThanOrEqual >=
op_Implicit implicit (vidgade) tvång
op_Increment ++ (prefix och postfix)
op_Inequality !=
op_LeftShift <<
op_LeftShiftAssignment (reserverad)
op_LessThan <
op_LessThanOrEqual <=
op_LogicalAnd (reserverad)
op_LogicalNot !
op_LogicalOr (reserverad)
op_MemberSelection (reserverad)
op_Modulus %
op_ModulusAssignment (reserverad)
op_MultiplicationAssignment (reserverad)
op_Multiply * (binärt)
op_OnesComplement ~
op_PointerDereference (reserverad)
op_PointerToMemberSelection (reserverad)
op_RightShift >>
op_RightShiftAssignment (reserverad)
op_SignedRightShift (reserverad)
op_Subtraction - (binärt)
op_SubtractionAssignment (reserverad)
op_True true
op_UnaryNegation - (unär)
op_UnaryPlus + (unär)
op_UnsignedRightShift (reserverad för framtida användning)
op_UnsignedRightShiftAssignment (reserverad)

15.4 Konstanter

En konstant är en klassmedlem som representerar ett konstant värde: ett värde som kan beräknas vid kompileringstid. En constant_declaration introducerar en eller flera konstanter av en viss typ.

constant_declaration
    : attributes? constant_modifier* 'const' type constant_declarators ';'
    ;

constant_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    ;

En constant_declaration kan innehålla en uppsättning attribut (§22), en new modifierare (§15.3.5) och någon av de tillåtna typerna av deklarerad tillgänglighet (§15.3.6). Attributen och modifierarna gäller för alla medlemmar som deklareras av constant_declaration. Även om konstanter anses vara statiska medlemmar, varken kräver eller tillåter en constant_declaration en static modifierare. Det är ett fel att samma modifierare visas flera gånger i en konstant deklaration.

Typen av constant_declaration anger vilken typ av medlemmar som infördes i deklarationen. Typen följs av en lista över constant_declarator s (§13.6.3), som var och en introduceraren ny medlem. En constant_declarator består av en identifierare som namnger medlemmen, följt av en "=" token, följt av en constant_expression (§12.23) som ger medlemmens värde.

Den typ som anges i en konstant deklaration ska vara sbyte, byte, short, ushort, int, uint, long, ulong, 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 readonly modifierare (§15.5.3) eller en volatile modifierare (§15.5.4), men inte båda. Attributen och modifierarna gäller för alla medlemmar som deklareras av field_declaration. Det är ett fel att samma modifierare visas flera gånger i en field_declaration.

Typen av field_declaration anger vilken typ av medlemmar som infördes i deklarationen. Typen följs av en lista över variable_declarators, som var och en introducerar en ny medlem. En variable_declarator består av en identifierare som namnger medlemmen, eventuellt följt av en "=" token och en variable_initializer (§15.5.6) som ger medlemmens ursprungliga värde.

Typen av fält ska vara minst lika tillgänglig som själva fältet (§7.5.5).

Värdet för ett fält erhålls i ett uttryck med hjälp av en simple_name (§12.8.4), en member_access (§12.8.7) eller en base_access (§12.8.15). Värdet på ett fält som inte är skrivskyddat ändras genom en tilldelning (§12.21). Värdet för ett icke-skrivskyddat fält kan både hämtas och ändras med postfix inkrement- och dekrementoperatorer (§12.8.16) och prefix inkrement- och dekrementoperatorer (§12.9.6).

En fältdeklaration som deklarerar flera fält motsvarar flera deklarationer av enkla fält med samma attribut, modifierare och typ.

Exempel:

class A
{
    public static int X = 1, Y, Z = 100;
}

motsvarar

class A
{
    public static int X = 1;
    public static int Y;
    public static int Z = 100;
}

slutexempel

15.5.2 Statiska fält och instansfält

När en fältdeklaration innehåller en static modifierare är fälten som introduceras av deklarationen statiska fält. När det inte finns någon static modifierare är fälten som introduceras av deklarationen instansfält. Statiska fält och instansfält är två av flera typer av variabler (§9) som stöds av C#, och ibland kallas de statiska variabler respektive instansvariabler.

Som förklaras i §15.3.8 innehåller varje instans av en klass en fullständig uppsättning instansfält för klassen, medan det bara finns en uppsättning statiska fält för varje icke-generisk klass eller stängd konstruktionstyp, oavsett antalet instanser av klassen eller den stängda konstruerade typen.

15.5.3 Skrivskyddade fält

15.5.3.1 Allmänt

När en field_declaration innehåller en readonly modifierare är fälten som introduceras av deklarationen readonly-fält. Direkttilldelningar till readonly-fält kan endast ske som en del av den deklarationen eller i en instanskonstruktor eller statisk konstruktor i samma klass. (A readonly-fält kan tilldelas flera gånger i dessa sammanhang.) Specifikt är direkta tilldelningar till ett skrivskyddat fält endast tillåtna i följande sammanhang:

  • I variable_declarator som introducerar fältet (genom att inkludera en variable_initializer i deklarationen).
  • För ett instansfält, i instanskonstruktorerna för klassen som innehåller fältdeklarationen; för ett statiskt fält, i den statiska konstruktorn för klassen som innehåller fältdeklarationen. Detta är också de enda sammanhangen där det är giltigt att överföra ett readonly fält som en ut- eller referensparameter.

Att försöka tilldela ett skrivskyddat fält eller använda det som en utdata- eller referensparameter i någon annan kontext är ett kompileringsfel.

15.5.3.2 Använda statiska readonly-fält för konstanter

Ett statiskt skrivskyddat fält är användbart när ett symboliskt namn för ett konstant värde önskas, men när typen av värdet inte tillåts i en const-deklaration eller när värdet inte kan beräknas vid kompileringstid.

Exempel: I följande kod

public class Color
{
    public static readonly Color Black = new Color(0, 0, 0);
    public static readonly Color White = new Color(255, 255, 255);
    public static readonly Color Red = new Color(255, 0, 0);
    public static readonly Color Green = new Color(0, 255, 0);
    public static readonly Color Blue = new Color(0, 0, 255);

    private byte red, green, blue;

    public Color(byte r, byte g, byte b)
    {
        red = r;
        green = g;
        blue = b;
    }
}

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 readonly-fält har olika semantik för binär versionshantering. När ett uttryck refererar till en konstant, hämtas värdet för konstanten vid kompileringstidpunkt, men när ett uttryck refererar till ett skrivskyddat fält, hämtas inte värdet för fältet förrän vid körningstid.

Exempel: Överväg ett program som består av två separata program:

namespace Program1
{
    public class Utils
    {
        public static readonly int x = 1;
    }
}

och

namespace Program2
{
    class Test
    {
        static void Main()
        {
            Console.WriteLine(Program1.Utils.X);
        }
    }
}

Namnrymderna Program1 och Program2 anger två program som kompileras separat. Eftersom Program1.Utils.X deklareras som ett static readonly-fält är värdet som genereras av Console.WriteLine-instruktionen inte känt vid kompileringstidpunkt, utan erhålls istället vid körningstid. 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 volatile modifierare, är de fält som introduceras av den deklarationen volatila fält. För icke-flyktiga fält kan optimeringstekniker som ändrar ordning på instruktioner leda till oväntade och oförutsägbara resultat i program med flera trådar som kommer åt fält utan synkronisering, till exempel de som tillhandahålls av lock_statement (§13.13). Dessa optimeringar kan utföras av kompilatorn, av körningssystemet eller av maskinvaran. För flyktiga fält är sådana omordningsoptimeringar begränsade:

  • En läsning av ett flyktigt fält kallas för en flyktig läsning. En flyktig läsning har "infångningssemantik"; det vill säga, det är garanterat att inträffa innan några minnesreferenser som är efterföljande i instruktionssekvensen.
  • En skrivning av ett flyktigt fält kallas för en flyktig skrivning. En volatil skrivning har "release-semantik"; det vill säga att den garanteras inträffa efter alla minnesreferenser före skrivinstruktionen i instruktionssekvensen.

Dessa begränsningar säkerställer att alla trådar observerar volatila skrivningar som utförs av andra trådar i den ordning de har gjorts. En överensstämmande implementering är inte skyldig att tillhandahålla en enda total ordning av flyktiga skrivningar sett från alla exekveringstrådar. Typen av ett flyktigt fält ska vara något av följande:

  • En referenstyp.
  • En type_parameter som är känd för att vara en referenstyp (§15.2.5).
  • Typen byte, sbyte, short, ushort, int, uint, char, float, bool, System.IntPtr eller 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 volatile, skulle det vara tillåtet för lagringen till result att vara synlig för huvudtråden efter lagringen 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ältinitialisering

Det initiala värdet för ett fält, oavsett om det är ett statiskt fält eller ett instansfält, är standardvärdet (§9.3) av fältets typ. Det går inte att observera värdet för ett fält innan den här standardinitieringen har inträffat, och ett fält är därför aldrig "onitialiserat".

Exempel: Exemplet

class Test
{
    static bool b;
    int i;

    static void Main()
    {
        Test t = new Test();
        Console.WriteLine($"b = {b}, i = {t.i}");
    }
}

genererar utdata

b = False, i = 0

eftersom b och i båda initieras automatiskt till standardvärden.

slutexempel

15.5.6 Variabelinitierare

15.5.6.1 Allmänt

Fältdeklarationer kan innehålla variable_initializers. För statiska fält motsvarar variabelinitierare tilldelningsinstruktioner som körs under klassinitiering. För fält gäller att variabelinitieringar motsvarar tilldelningsuttryck som utförs när en instans av klassen skapas.

Exempel: Exemplet

class Test
{
    static double x = Math.Sqrt(2.0);
    int i = 100;
    string s = "Hello";

    static void Main()
    {
        Test a = new Test();
        Console.WriteLine($"x = {x}, i = {a.i}, s = {a.s}");
    }
}

genererar utdata

x = 1.4142135623730951, i = 100, s = Hello

eftersom en tilldelning till x inträffar när statiska fältinitierare körs och tilldelningar till i och s inträffar när instansfältinitierare körs.

slutexempel

Standardvärdets initiering som beskrivs i §15.5.5 sker för alla fält, inklusive fält som har variabelinitierare. När en klass initieras initieras därför alla statiska fält i den klassen först till standardvärdena och sedan körs de statiska fältinitierarna i textordning. På samma sätt initieras alla instansfält i den instansen till standardvärdena när en instans av en klass skapas, och sedan körs instansfältinitierarna i textordning. När det finns fältdeklarationer i flera partiella typdeklarationer för samma typ är ordningen på delarna ospecificerad. Dock körs fältinitierare i ordning inom varje del.

Det är möjligt att statiska fält med variabelinitierare observeras i deras standardvärdetillstånd.

Exempel: Detta avråds dock starkt från som en fråga om stil. Exemplet

class Test
{
    static int a = b + 1;
    static int b = a + 1;

    static void Main()
    {
        Console.WriteLine($"a = {a}, b = {b}");
    }
}

uppvisar detta beteende. Trots cirkulära definitioner av a 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 initialiseringen för a körs är värdet av b noll, och därför initialiseras a till 1. När initiatorn för b körs, är värdet av a redan 1, och därför initieras 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 producera antingen resultatet:

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 vilken som helst ordning; de är endast begränsade till att ske före referenser till de fälten. Men i exemplet:

class Test
{
    static void Main()
    {
        Console.WriteLine($"{B.Y} {A.X}");
    }

    public static int F(string s)
    {
        Console.WriteLine(s);
        return 1;
    }
}

class A
{
    static A() {}
    public static int X = Test.F("Init A");
}

class B
{
    static B() {}
    public static int Y = Test.F("Init B");
}

Utdata skall vara:

Init B
Init A
1 1

eftersom reglerna för när statiska konstruktorer körs (enligt definitionen i §15.12) anger att B's statiska konstruktor (och därmed B's statiska fältinitierare) ska köras före A's statiska konstruktor och fältinitierare.

slutexempel

15.5.6.3 Initialisering av instansfält

Instansfältvariabelinitierare för en klass motsvarar en sekvens av tilldelningar som körs omedelbart vid inmatning till någon av instanskonstruktörerna (§15.11.3) för den klassen. Inom en partiell klass anges innebörden av "textordning" av §15.5.6.1. Variabelinitierarna körs i textordningen där de visas i klassdeklarationen (§15.5.6.1). Processen för att skapa och initiera klassinstanser beskrivs ytterligare i §15.11.

En variabelinitierare för ett instansfält kan inte referera till den instans som skapas. Det är alltså ett kompileringsfel att referera this till i en variabelinitierare, eftersom det är ett kompileringsfel för en variabelinitierare som refererar till alla instansmedlemmar via en simple_name.

Exempel: I följande kod

class A
{
    int x = 1;
    int y = x + 1;     // Error, reference to instance member of this
}

variabelinitieraren för y resulterar i ett kompileringsfel eftersom den refererar till en medlem i instansen som skapas.

slutexempel

15.6 Metoder

15.6.1 Allmänt

En metod är en medlem som implementerar en beräkning eller åtgärd som kan utföras av ett objekt eller en klass. Metoder deklareras med method_declaration s:

method_declaration
    : attributes? method_modifiers return_type method_header method_body
    | attributes? ref_method_modifiers ref_kind ref_return_type method_header
      ref_method_body
    ;

method_modifiers
    : method_modifier* 'partial'?
    ;

ref_kind
    : 'ref'
    | 'ref' 'readonly'
    ;

ref_method_modifiers
    : ref_method_modifier*
    ;

method_header
    : member_name '(' parameter_list? ')'
    | member_name type_parameter_list '(' parameter_list? ')'
      type_parameter_constraints_clause*
    ;

method_modifier
    : ref_method_modifier
    | 'async'
    ;

ref_method_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | 'readonly'        // direct struct members only
    | unsafe_modifier   // unsafe code support
    ;

return_type
    : ref_return_type
    | 'void'
    ;

ref_return_type
    : type
    ;

member_name
    : identifier
    | interface_type '.' identifier
    ;

method_body
    : block
    | '=>' null_conditional_invocation_expression ';'
    | '=>' expression ';'
    | ';'
    ;

ref_method_body
    : block
    | '=>' 'ref' variable_reference ';'
    | ';'
    ;

Grammatikanteckningar:

  • unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).
  • När man känner igen en method_body, om både null_conditional_invocation_expression och expression alternativ är tillämpliga, ska det förstnämnda väljas.

Obs! Överlappningen av och prioriteten mellan alternativen här är enbart för beskrivande bekvämlighet. Grammatikreglerna kan utarbetas för att ta bort överlappningen. ANTLR och andra grammatiksystem använder samma bekvämlighet, så method_body har de angivna semantiken automatiskt. slutkommentar

En method_declaration kan innehålla en uppsättning attribut (§22) och en av de tillåtna typerna av deklarerad tillgänglighet (§15.3.6), new (§15.3.5), static (§15.6.3), virtual (§1 5.6.4), override (§15.6.5), sealed (§15.6.6), abstract (§15.6.7), extern (§15.6.8) och async (§15.14). Dessutom kan en method_declaration som finns direkt av en struct_declaration inkludera readonly modifieraren (§16.4.12).

En deklaration har en giltig kombination av modifierare om allt av följande är sant:

  • Deklarationen innehåller en giltig kombination av åtkomstmodifierare (§15.3.6).
  • Deklarationen innehåller inte samma modifierare flera gånger.
  • Deklarationen innehåller högst en av följande modifierare: static, 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 innehåller modifieraren sealed, så innehåller deklarationen också modifieraren override.
  • Om deklarationen partial innehåller modifieraren så innehåller den inte någon av de följande modifierarna: new, public, protected, internal, private, virtual, sealed, override, abstract eller extern.

Metoderna klassificeras enligt vad, om något, de returnerar:

  • Om ref finns är metoden returns-by-ref och returnerar en variabelreferens, som eventuellt kan vara skrivskyddad.
  • Annars, om return_type är void, då är metoden returns-no-value och den returnerar inte ett värde.
  • Annars är metoden returns-by-value och returnerar ett värde.

Den return_type av en metoddeklaration som returnerar genom värde eller returnerar inget värde anger vilken typ av resultat som, om det finns något, returneras av metoden. Endast en metod som inte returnerar ett värde får innehålla partial modifieraren (§15.6.9). Om deklarationen async innehåller modifieraren ska return_type vara void eller metoden returnerar per värde och returtypen är en uppgiftstyp (§15.14.1).

ref_return_type för en metoddeklaration som returnerar via referens anger typen av variabeln som refereras av variable_reference som returneras av metoden.

En allmän metod är en metod vars deklaration innehåller en type_parameter_list. Detta anger typparametrarna för metoden. De valfria type_parameter_constraints_clause specificerar begränsningarna för typparametrarna.

En generisk method_declaration för en explicit implementering av en gränssnittsmedlem får inte ha några type_parameter_constraints_clause; deklarationen ärver eventuella begränsningar från begränsningarna för gränssnittsmetoden.

På samma sätt ska en metoddeklaration med override modifieraren inte ha några type_parameter_constraints_clauseoch begränsningarna för metodens typparametrar ärvs från den virtuella metod som åsidosätts.

member_name anger namnet på metoden. Om inte metoden är en explicit implementering av gränssnittsmedlemmar (§18.6.2) är member_name helt enkelt en identifierare.

För en explicit implementering av gränssnittsmedlemmar består member_name av en interface_type följt av ett "." och en identifierare. I detta fall får deklarationen inte innehålla några andra modifierare än (möjligen) extern eller async.

Den valfria parameter_list anger metodens parametrar (§15.6.2).

return_type eller ref_return_type, och var och en av de typer som anges i parameter_list av en metod, ska vara minst lika tillgänglig som själva metoden (§7.5.5).

Den method_body av en metod som returnerar ett värde eller inget värde är antingen ett semikolon, en blockkropp eller en uttryckskropp. En blocktext består av ett block som anger vilka instruktioner som ska köras när metoden anropas. En uttryckstext består av =>, följt av en null_conditional_invocation_expression eller ett uttryck och ett semikolon, och anger ett enda uttryck som ska utföras när metoden anropas.

För abstrakta och externa metoder består method_body helt enkelt av ett semikolon. För partiella metoder kan method_body bestå av antingen ett semikolon, en blockkropp eller en uttryckstext. För alla andra metoder är method_body antingen en blocktext eller en uttryckstext.

Om method_body består av semikolon ska deklarationen inte innehålla async modifieraren.

Ref_method_body för en metod som returnerar med referens är antingen ett semikolon, en blockkropp eller en uttryckskropp. En blocktext består av ett block som anger vilka instruktioner som ska köras när metoden anropas. En uttryckstext består av =>, följt av ref, en variable_reference och ett semikolon, och anger en enda variable_reference för att utvärdera när metoden anropas.

För abstrakta och externa metoder består ref_method_body helt enkelt av ett semikolon. För alla andra metoder är ref_method_body antingen en blocktext eller en uttryckstext.

Namnet, antalet typparametrar och parameterlistan för en metod definierar metodens signatur (§7.6). Mer specifikt består signaturen för en metod av dess namn, antalet av dess typparametrar och antalet, parameter_mode_modifiers (§15.6.2.1) och typer av dess parametrar. Returtypen är inte en del av en metods signatur, inte heller namnen på parametrarna, namnen på typparametrarna eller begränsningarna. När en parametertyp refererar till en typparameter för metoden används ordningspositionen för typparametern (inte namnet på typparametern) för typjämförelse.

Namnet på en metod ska skilja sig från namnen på alla andra icke-metoder som deklareras i samma klass. Dessutom ska signaturen för en metod skilja sig från signaturerna för alla andra metoder som deklarerats i samma klass, och två metoder som deklareras i samma klass får inte ha signaturer som endast skiljer sig åt med in, 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 konstant_uttryck
  • 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 identitets- eller nullbarhetskonvertering till typen av parametern.

Om valfria parametrar inträffar i en implementering av partiell metoddeklaration (§15.6.9), en explicit implementering av gränssnittsmedlemmar (§18.6.2), en indexerardeklaration med en parameter (§15.9), eller i en operatörsdeklaration (§15.10.1) bör en kompilator ge en varning, eftersom dessa medlemmar aldrig kan åberopas på ett sätt som tillåter att argument utelämnas.

En parameter_array består av en valfri uppsättning attribut (§22), en params modifierare, en array_type och en identifierare. En parametermatris deklarerar en enskild parameter av den angivna matristypen med det angivna namnet. array_type för en parametermatris ska vara en endimensionell matristyp (§17.2). I ett metodanrop tillåter en parametermatris att antingen ett enda argument av den angivna matristypen anges, eller så kan noll eller fler argument av matriselementtypen anges. Parametermatriser beskrivs ytterligare i §15.6.2.4.

En parameter_array kan inträffa efter en valfri parameter, men kan inte ha ett standardvärde – utelämnandet av argument för en parameter_array skulle i stället leda till att en tom matris skapas.

Exempel: Följande illustrerar olika typer av parametrar:

void M<T>(
    ref int i,
    decimal d,
    bool b = false,
    bool? n = false,
    string s = "Hello",
    object o = null,
    T t = default(T),
    params int[] a
) { }

I parameter_list för M är i en obligatorisk ref parameter, d är en obligatorisk värdeparameter, medan b, s, o 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. Metodens brödtext, om det finns någon, betraktas som inbäddad i detta deklarationsutrymme. Det är ett fel att två medlemmar i ett metoddeklarationsutrymme har samma namn.

Ett metodanrop (§12.8.10.2) skapar en kopia, specifik för anropet, av parametrarna och de lokala variablerna för metoden, och argumentlistan över anropet tilldelar värden eller variabelreferenser till de nyligen skapade parametrarna. Inom blocket av en metod kan parametrar refereras till med sina identifierare i simple_name-uttryck (§12.8.4).

Följande typer av parametrar finns:

Obs! Enligt beskrivningen i §7.6 är in, out, och ref modifierare en del av en metods signatur, men params är inte det. slutkommentar

15.6.2.2 Värdeparametrar

En parameter som deklareras utan modifierare är en värdeparameter. En värdeparameter är en lokal variabel som hämtar sitt ursprungliga värde från motsvarande argument som anges i metodanropet.

För bestämda tilldelningsregler, se §9.2.5.

Motsvarande argument i ett metodanrop ska vara ett uttryck som implicit är konvertibelt (§10.2) till parametertypen.

En metod tillåts tilldela nya värden till en värdeparameter. Sådana tilldelningar påverkar bara den lokala lagringsplats som representeras av värdeparametern – de har ingen effekt på det faktiska argumentet som anges i metodanropet.

15.6.2.3 Bireferensparametrar

15.6.2.3.1 Allmänt

Indata-, utdata- och referensparametrar är referensparametrar. En bireferensparameter är en lokal referensvariabel (§9.7); den första referensen hämtas från motsvarande argument som anges i metodanropet.

Obs! Referensen för en bireferensparameter kan ändras med referenstilldelningsoperatorn (= ref).

När en parameter är en referensparameter ska motsvarande argument i ett metodanrop bestå av det motsvarande nyckelordet, in, ref, eller out, följt av en variabelreferens (§9.5) av samma typ som parametern. Men när parametern är en in parameter kan argumentet vara ett uttryck för vilket en implicit konvertering (§10.2) finns från det argumentuttrycket till typen av motsvarande parameter.

Bireferensparametrar tillåts inte för funktioner som deklareras som iterator (§15.15) eller asynkron funktion (§15.14).

I en metod som tar flera bireferensparametrar är det möjligt att flera namn representerar samma lagringsplats.

15.6.2.3.2 Inmatningsparametrar

En parameter som deklareras med en in-modifierare är en indataparameter. Argumentet som motsvarar en indataparameter är antingen en variabel som finns vid tidpunkten för metodanropet, eller en som skapats av implementeringen (§12.6.2.3) i metodens anrop. För bestämda tilldelningsregler, se §9.2.8.

Det är ett kompileringsfel att ändra värdet för en indataparameter.

Obs! Det primära syftet med indataparametrar är för effektivitet. När typen av en metodparameter är en stor struct (vad gäller minnesbehov) är det användbart att kunna undvika att kopiera hela värdet för argumentet när du anropar metoden. Med indataparametrar kan metoder referera till befintliga värden i minnet, samtidigt som de skyddar mot oönskade ändringar av dessa värden. slutkommentar

Referensparametrar för 15.6.2.3.3

En parameter som deklareras med en ref modifierare är en referensparameter. För regler för bestämd tilldelning, se §9.2.6.

Exempel: Exemplet

class Test
{
    static void Swap(ref int x, ref int y)
    {
        int temp = x;
        x = y;
        y = temp;
    }

    static void Main()
    {
        int i = 1, j = 2;
        Swap(ref i, ref j);
        Console.WriteLine($"i = {i}, j = {j}");
    }
}

genererar utdata

i = 2, j = 1

För anropet av Swap i Main, representerar xi och y representerar j. Anropet har därför effekten att växla värdena på i 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 utgångsparameter. För bestämda tilldelningsregler, se §9.2.7.

En metod som deklareras som en partiell metod (§15.6.9) får inte ha utdataparametrar.

Obs! Utdataparametrar används vanligtvis i metoder som producerar flera returvärden. slutkommentar

Exempel:

class Test
{
    static void SplitPath(string path, out string dir, out string name)
    {
        int i = path.Length;
        while (i > 0)
        {
            char ch = path[i - 1];
            if (ch == '\\' || ch == '/' || ch == ':')
            {
                break;
            }
            i--;
        }
        dir = path.Substring(0, i);
        name = path.Substring(i);
    }

    static void Main()
    {
        string dir, name;
        SplitPath(@"c:\Windows\System\hello.txt", out dir, out name);
        Console.WriteLine(dir);
        Console.WriteLine(name);
    }
}

Exemplet genererar utdata:

c:\Windows\System\
hello.txt

Observera att variablerna dir 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 fyraelement 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 former beaktas därför inte vid överladdningsmatchning, och det första och tredje metodanropet väljer således de vanliga metoderna. När en klass deklarerar en metod med en parametermatris är det inte ovanligt att även inkludera några av de expanderade formulären som vanliga metoder. På så sätt kan du undvika allokeringen av en matrisinstans som inträffar när en expanderad form av en metod med en parametermatris anropas.

slutexempel

En matris är en referenstyp, så värdet som skickas för en parametermatris kan vara null.

Exempel: Exemplet:

class Test
{
    static void F(params string[] array) =>
        Console.WriteLine(array == null);

    static void Main()
    {
        F(null);
        F((string) null);
    }
}

genererar utdata:

True
False

Det andra anropet genererar False som det är likvärdigt 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. Tvetyydigheten utgör inget problem, dock, eftersom den kan lösas genom att infoga en typkonvertering vid behov.

Exempel: Exemplet

class Test
{
    static void F(params object[] args)
    {
        foreach (object o in args)
        {
            Console.Write(o.GetType().FullName);
            Console.Write(" ");
        }
        Console.WriteLine();
    }

    static void Main()
    {
        object[] a = {1, "Hello", 123.456};
        object o = a;
        F(a);
        F((object)a);
        F(o);
        F((object[])o);
    }
}

genererar utdata

System.Int32 System.String System.Double
System.Object[]
System.Object[]
System.Int32 System.String System.Double

I de första och sista anropen av 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 överbelastningslösning. 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).

Vid ett virtuellt metodanrop avgör instansens körtidstyp vilken faktisk metodimplementering som ska anropas. I en icke-virtuell metodanrop är kompileringstidstypen för instansen den avgörande faktorn. När en metod med namnet N anropas med en argumentlista A på en instans med en kompileringstyp C och en körningstyp R (där R är antingen C eller en klass härledd från C) bearbetas anropet enligt följande:

  • Vid bindningstid tillämpas överbelastningslösning på C, N och A för att välja en specifik metod M från den uppsättning metoder som deklareras i och ärvs av C. Detta beskrivs i §12.8.10.2.
  • Sedan vid körtid:
    • Om M är en icke-virtuell metod M anropas.
    • Annars är M en virtuell metod, och den mest härledda implementeringen av M med avseende på R 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 den virtuella deklarationen av M, är detta den fullständigt 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 påståendet 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 D innehåller två virtuella metoder med samma signatur: Den som introducerades av A och den som introducerades av C. 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 lokaliseras på det ovan beskrivna sättet.
  • Det finns exakt en sådan åsidosatt basmetod. Den här begränsningen gäller endast om basklasstypen är en konstruerad typ där ersättningen av typargument gör signaturen för två metoder densamma.
  • Den åsidosatta basmetoden är en virtuell, abstrakt eller överskriven metod. Med andra ord kan den åsidosatta basmetoden inte vara statisk eller icke-virtuell.
  • Den åsidosatta basmetoden är inte en sluten metod.
  • Det finns en identitetskonvertering mellan returtypen för den åsidosatta basmetoden och åsidosättningsmetoden.
  • Åsidosättningsdeklarationen och den åsidosatta basmetoden har samma angivna åtkomsträttighet. Med andra ord kan en åsidosättningsdeklaration inte ändra tillgängligheten för den virtuella metoden. Men om den åsidosatta basmetoden är skyddad internt och den deklareras i en annan sammansättning än den sammansättning som innehåller åsidosättningsdeklarationen ska åsidosättningsdeklarationens deklarerade tillgänglighet skyddas.
  • Åsidosättningsdeklarationen specificerar inga type_parameter_constraints_clause. I stället ärvs begränsningarna från den åsidosatta basmetoden. Begränsningar som är typparametrar i den åsidosatta metoden kan ersättas med typargument i den ärvda begränsningen. Detta kan leda till begränsningar som inte är giltiga när de uttryckligen anges, till exempel värdetyper eller förseglade typer.

Exempel: Följande visar hur de övergripande reglerna fungerar för generiska klasser:

abstract class C<T>
{
    public virtual T F() {...}
    public virtual C<T> G() {...}
    public virtual void H(C<T> x) {...}
}

class D : C<string>
{
    public override string F() {...}            // Ok
    public override C<string> G() {...}         // Ok
    public override void H(C<T> x) {...}        // Error, should be C<string>
}

class E<T,U> : C<U>
{
    public override U F() {...}                 // Ok
    public override C<U> G() {...}              // Ok
    public override void H(C<T> x) {...}        // Error, should be C<U>
}

slutexempel

En åsidosättningsdeklaration kan komma åt den åsidosatta basmetoden med hjälp av en base_access (§12.8.15).

Exempel: I följande kod

class A
{
    int x;

    public virtual void PrintFields() => Console.WriteLine($"x = {x}");
}

class B : A
{
    int y;

    public override void PrintFields()
    {
        base.PrintFields();
        Console.WriteLine($"y = {y}");
    }
}

anropet base.PrintFields() 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 skrivits som B, skulle det rekursivt anropa metoden ((A)this).PrintFields() som deklarerats i PrintFields, inte den som deklarerats i B, eftersom A är virtuell och körningstypen av PrintFields är ((A)this).

slutexempel

Endast genom att inkludera en override modifierare kan en metod åsidosätta en annan metod. I alla andra fall döljer en metod med samma signatur som en ärvd metod helt enkelt den ärvda metoden.

Exempel: I följande kod

class A
{
    public virtual void F() {}
}

class B : A
{
    public virtual void F() {} // Warning, hiding inherited F()
}

metoden F i B innehåller ingen override-modifierare och därför åsidosätter den inte metoden F 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 är deklarationen F i C tillåten att åsidosätta den ä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 modifikatorn förhindrar C från vidare åsidosättning av 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 måste icke-abstrakta härledda klasser själva implementera metoden genom att åsidosätta den. Eftersom en abstrakt metod inte ger någon faktisk implementering består metodtexten för en abstrakt metod helt enkelt av ett semikolon.

Abstrakta metoddeklarationer tillåts endast i abstrakta klasser (§15.2.2.2).

Exempel: I följande kod

public abstract class Shape
{
    public abstract void Paint(Graphics g, Rectangle r);
}

public class Ellipse : Shape
{
    public override void Paint(Graphics g, Rectangle r) => g.DrawEllipse(r);
}

public class Box : Shape
{
    public override void Paint(Graphics g, Rectangle r) => g.DrawRect(r);
}

klassen Shape definierar den abstrakta uppfattningen om ett geometriskt formobjekt som kan måla sig själv. 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) att hänvisa till en abstrakt metod.

Exempel: I följande kod

abstract class A
{
    public abstract void F();
}

class B : A
{
    // Error, base.F is abstract
    public override void F() => base.F();
}

ett kompileringsfel rapporteras för anropet base.F() eftersom det refererar till en abstrakt metod.

slutexempel

En abstrakt metoddeklaration tillåts åsidosätta en virtuell metod. På så sätt kan en abstrakt klass framtvinga omimplementering av metoden i härledda klasser och göra den ursprungliga implementeringen av metoden otillgänglig.

Exempel: I följande kod

class A
{
    public virtual void F() => Console.WriteLine("A.F");
}

abstract class B: A
{
    public abstract override void F();
}

class C : B
{
    public override void F() => Console.WriteLine("C.F");
}

klass A deklarerar en virtuell metod, 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. Identifieraren partial identifieras som ett kontextuellt nyckelord (§6.4.4) i en metoddeklaration endast om den visas omedelbart före nyckelordet void. En partiell metod kan inte uttryckligen implementera gränssnittsmetoder.

Det finns två typer av partiella metoddeklarationer: Om brödtexten i metoddeklarationen är ett semikolon, sägs deklarationen vara en definierande partiell metoddeklaration. Om brödtexten är annan än ett semikolon, sägs deklarationen vara en genomförandedelmetoddeklaration. I delar av en typdeklaration ska det endast finnas en som definierar en partiell metoddeklaration med en viss signatur, och det får högst finnas en genomförandedeklaration för partiell metod med en given underskrift. Om en implementerande delmetodsdeklaration anges ska det finnas en motsvarande definierande delmetodsdeklaration och deklarationerna ska överensstämma enligt följande.

  • Deklarationerna ska ha samma modifierare (men inte nödvändigtvis i samma ordning), metodnamn, antal typparametrar och antal parametrar.
  • Motsvarande parametrar i deklarationerna ska ha samma modifierare (men inte nödvändigtvis i samma ordning) och samma typer, eller identitetskonverterbara typer (modulodifferenser i typparameternamn).
  • Motsvarande typparametrar i deklarationerna ska ha samma begränsningar (moduloskillnader i typparameternamn).

En partiell metoddeklaration för implementering kan visas i samma del som motsvarande definierande partiell metoddeklaration.

Endast en definierande partiell metod deltar i överlagringsupplösning. Oavsett om en implementeringsdeklaration ges eller inte kan anropsuttryck leda till anrop av en partiell metod. Eftersom en partiell metod alltid returnerar voidär sådana anropsuttryck alltid uttrycksuttryck. Eftersom en partiell metod är implicit privatekommer sådana påståenden alltid att förekomma inom en av de delar av typdeklarationen där den partiella metoden deklareras.

Obs! Definitionen av matchande definition och implementering av partiella metoddeklarationer kräver inte att parameternamn matchar. Detta kan ge ett överraskande, om än väldefinierat, beteende när namngivna argument (§12.6.2.1) används. Till exempel, med tanke på den definierande partiella metoddeklarationen för M i en fil och implementeringen av partiell metoddeklaration i en annan fil:

// File P1.cs:
partial class P
{
    static partial void M(int x);
}

// File P2.cs:
partial class P
{
    static void Caller() => M(y: 0);
    static partial void M(int y) {}
}

är ogiltigt eftersom anropet använder argumentnamnet från implementeringen och inte den definierande partiella metoddeklarationen.

slutkommentar

Om ingen del av en partiell typdeklaration innehåller en implementeringsdeklaration för en viss partiell metod, tas alla uttrycksinstruktor som anropar den helt enkelt bort från den kombinerade typdeklarationen. Därför har anropsuttrycket, inklusive alla eventuella underuttryck, ingen effekt vid körning. Själva den partiella metoden tas också bort och kommer inte att vara medlem i den kombinerade typdeklarationen.

Om det finns en implementeringsdeklaration för en viss partiell metod behålls anropen för de partiella metoderna. Den partiella metoden ger upphov till en metoddeklaration som liknar den partiella metoddeklarationen för implementering med undantag för följande:

  • Modifieraren partial ingår inte.

  • Attributen i den resulterande metoddeklarationen är de kombinerade attributen för den definierande och implementerande partiella metoddeklarationen i ospecificerad ordning. Dubbletter tas inte bort.

  • Attributen för parametrarna i den resulterande metoddeklarationen är de kombinerade attributen för motsvarande parametrar för definitionen och implementeringen av partiell metoddeklaration i ospecificerad ordning. Dubbletter tas inte bort.

Om en definierande deklaration men inte en implementeringsdeklaration anges för en partiell metod Mgäller följande begränsningar:

  • Det är ett kompileringstidfel att skapa en delegering från M (§12.8.17.5).

  • Det är ett kompileringsfel att referera till M i en anonym funktion som konverteras till en uttrycksträdstyp (§8.6).

  • Uttryck som inträffar som en del av ett anrop av M påverkar inte det bestämda tilldelningstillståndet (§9.4), vilket potentiellt kan leda till kompileringsfel.

  • M kan inte vara startpunkten för en ansökan (§7.1).

Partiella metoder är användbara för att tillåta att en del av en typdeklaration anpassar beteendet för en annan del, t.ex. en som genereras av ett verktyg. Överväg följande partiella klassdeklaration:

partial class Customer
{
    string name;

    public string Name
    {
        get => name;
        set
        {
            OnNameChanging(value);
            name = value;
            OnNameChanged();
        }
    }

    partial void OnNameChanging(string newName);
    partial void OnNameChanged();
}

Om den här klassen kompileras utan några andra delar tas de definierande partiella metoddeklarationerna och deras anrop bort och den resulterande kombinerade klassdeklarationen motsvarar följande:

class Customer
{
    string name;

    public string Name
    {
        get => name;
        set => name = value;
    }
}

Anta dock att en annan del ges som tillhandahåller implementeringsdeklarationer av de partiella metoderna:

partial class Customer
{
    partial void OnNameChanging(string newName) =>
        Console.WriteLine($"Changing {name} to {newName}");

    partial void OnNameChanged() =>
        Console.WriteLine($"Changed to {name}");
}

Sedan motsvarar den resulterande kombinerade klassdeklarationen följande:

class Customer
{
    string name;

    public string Name
    {
        get => name;
        set
        {
            OnNameChanging(value);
            name = value;
            OnNameChanged();
        }
    }

    void OnNameChanging(string newName) =>
        Console.WriteLine($"Changing {name} to {newName}");

    void OnNameChanged() =>
        Console.WriteLine($"Changed to {name}");
}

15.6.10 Tilläggsmetoder

När den första parametern för en metod innehåller this modifieraren sägs den metoden vara en tilläggsmetod. Tilläggsmetoder ska endast deklareras i icke-generiska, icke-kapslade statiska klasser. Den första parametern för en tilläggsmetod är begränsad enligt följande:

  • Det kan bara vara en indataparameter om den har en värdetyp
  • Det kan bara vara en referensparameter om den har en värdetyp eller har en allmän typ som är begränsad till struct
  • Det ska inte vara en pekartyp.

Exempel: Följande är ett exempel på en statisk klass som deklarerar två tilläggsmetoder:

public static class Extensions
{
    public static int ToInt32(this string s) => Int32.Parse(s);

    public static T[] Slice<T>(this T[] source, int index, int count)
    {
        if (index < 0 || count < 0 || source.Length - index < count)
        {
            throw new ArgumentException();
        }
        T[] result = new T[count];
        Array.Copy(source, index, result, 0, count);
        return result;
    }
}

slutexempel

En tilläggsmetod är en vanlig statisk metod. Där dess omslutande statiska klass finns i omfånget kan dessutom en tilläggsmetod anropas med instansmetodens anropssyntax (§12.8.10.3), med mottagaruttrycket som första argument.

Exempel: Följande program använder de tilläggsmetoder som deklareras ovan:

static class Program
{
    static void Main()
    {
        string[] strings = { "1", "22", "333", "4444" };
        foreach (string s in strings.Slice(1, 2))
        {
            Console.WriteLine(s.ToInt32());
        }
    }
}

Metoden Slice är tillgänglig på string[], 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.14.1). Annars är den effektiva returtypen för en icke-asynkron metod dess returtyp och den effektiva returtypen för en asynkron metod med returtyp «TaskType»<T>(§15.14.1) är T.

När den effektiva returtypen för en metod är void och metoden har en blockkropp, får return-satser (§13.10.5) i blocket inte ange ett uttryck. Om körningen av blocket för en void-metod slutförs normalt (det vill säga att kontrollen flödar från slutet av metodkroppen), återgår metoden helt enkelt till anroparen.

När den effektiva returtypen för en metod är void och metoden har en uttryckstext, ska uttrycket E vara en statement_expression, och brödtexten är exakt likvärdig med en blocktext i formuläret { E; }.

För en metod för avkastning per värde (§15.6.1) ska varje returutdrag i metodens brödtext ange ett uttryck som implicit är konvertibelt till den effektiva returtypen.

För en return-by-ref-metod (§15.6.1) ska varje returutdrag i metodens brödtext ange ett uttryck vars typ är av den effektiva returtypen och ha en referenssäker kontext av anroparkontext (§9.7.2).

För metoder som returnerar via värde och referens ska slutpunkten i metodkroppen inte vara nåbar. Med andra ord är det inte tillåtet för programflödet att fortsätta efter slutet av metodens kodblock.

Exempel: I följande kod

class A
{
    public int F() {} // Error, return value required

    public int G()
    {
        return 1;
    }

    public int H(bool b)
    {
        if (b)
        {
            return 1;
        }
        else
        {
            return 0;
        }
    }

    public int I(bool b) => b ? 1 : 0;
}

den värdeåtergivande F-metoden resulterar i ett kompileringsfel eftersom kontrollen kan flöda utanför slutet av metodkroppen. Metoderna G och H är korrekta eftersom alla möjliga exekveringsvägar slutar med en retursats som specificerar ett returvärde. Metoden I är korrekt eftersom dess kropp motsvarar ett block med endast en retursats i den.

slutexempel

15.7 Egenskaper

15.7.1 Allmänt

En egenskap är en medlem som ger åtkomst till en egenskap för ett objekt eller en klass. Exempel på egenskaper är längden på en sträng, storleken på ett teckensnitt, beskrivningen av ett fönster och namnet på en kund. Egenskaper är en naturlig förlängning av fält – båda är namngivna medlemmar med associerade typer, och syntaxen för att komma åt fält och egenskaper är densamma. Till skillnad från fält anger egenskaperna dock inte lagringsplatser. Istället har egenskaper accessorer som anger de instruktioner som ska köras när deras värden läses eller skrivs. Egenskaper ger därmed en mekanism för att associera åtgärder med läsning och skrivning av ett objekts eller en klasss egenskaper. Dessutom tillåter de att sådana egenskaper beräknas.

Egenskaper deklareras med property_declaration s:

property_declaration
    : attributes? property_modifier* type member_name property_body
    | attributes? property_modifier* ref_kind type member_name ref_property_body
    ;    

property_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | 'readonly'        // direct struct members only
    | unsafe_modifier   // unsafe code support
    ;
    
property_body
    : '{' accessor_declarations '}' property_initializer?
    | '=>' expression ';'
    ;

property_initializer
    : '=' variable_initializer ';'
    ;

ref_property_body
    : '{' ref_get_accessor_declaration '}'
    | '=>' 'ref' variable_reference ';'
    ;

unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).

En property_declaration kan innehålla en uppsättning attribut (§22) och någon av de tillåtna typerna av deklarerad tillgänglighet (§15.3.6), new (§15.3.5), static (§15.7.2), virtual (§15.6.4, §15.7.6), override (§15.6.5, §15.7.6), sealed (§15.6.6), abstract (§15.6.7, §15.7.6) och extern (§15.6.8). Dessutom kan en property_declaration som finns direkt av en struct_declaration inkludera readonly modifieraren (§16.4.11).

  • Den första deklarerar en egenskap med ett icke-referensbaserat värde. Dess värde har typen type. Den här typen av egenskap kan vara läsbar och/eller skrivbar.
  • Den andra deklarerar en egenskap som har referensvärde. Dess värde är en variabelreferens (§9.5), som kan vara readonly, till en variabel av typen typ. Den här typen av egenskap är endast läsbar.

En property_declaration kan innehålla en uppsättning attribut (§22) och någon av de tillåtna typerna av deklarerad tillgänglighet (§15.3.6), new (§15.3.5), static (§15.7.2), virtual (§15.6.4, §15.7.6), override (§15.6.5, §15.7.6), sealed (§15.6.6), abstract (§15.6.7, §15.7.6) och extern (§15.6.8) modifierare.

Egenskapsdeklarationer omfattas av samma regler som metoddeklarationer (§15.6) när det gäller giltiga kombinationer av modifierare.

Member_name (§15.6.1) anger namnet på egenskapen. Om inte egenskapen är en explicit implementering av gränssnittsmedlemmar är member_name helt enkelt en identifierare. För en explicit implementering av gränssnittsmedlemmar (§18.6.2) består member_name av en interface_type följt av en "." och en identifierare.

Fastighetens typ ska vara minst lika tillgänglig som själva fastigheten (§7.5.5).

En property_body kan antingen bestå av en satstext eller en uttryckstext. I en satstext, där accessor_declarations ska omges av "{" och "}" tokens, deklareras accessorerna (§15.7.3) för egenskapen. Accessorerna specificerar de körbara instruktioner som är associerade med att läsa och skriva egenskapen.

I en property_body en uttryckstext som består av => följt av ett uttryckE och ett semikolon är exakt likvärdigt med uttryckstexten { get { return E; } }, och kan därför endast användas för att ange skrivskyddade egenskaper där resultatet av get-accessorn ges av ett enda uttryck.

En property_initializer får endast ges för en automatiskt implementerad egenskap (§15.7.4) och orsakar initieringen av det underliggande fältet för sådana egenskaper med det värde som anges av uttrycket.

En ref_property_body kan antingen bestå av ett instruktionsblock eller en uttryckstext. I en satskropp deklarerar en get_accessor_declaration get-accessorn (§15.7.3) av egenskapen. Accessorn specificerar de körbara instruktionerna som är kopplade till läsning av egenskapen.

I en ref_property_body består en uttryckskropp av => följt av ref, en variable_referenceV och ett semikolon som är exakt likvärdigt med statementskroppen { get { return ref V; } }.

Obs! Även om syntaxen för att komma åt en egenskap är densamma som för ett fält klassificeras inte en egenskap som en variabel. Därför är det inte möjligt att skicka en egenskap som ett in, out, eller ref argument om inte egenskapen är ref-värde och därför returnerar en variabelreferens (§9.7). slutkommentar

När en egenskapsdeklaration innehåller en extern modifierare sägs egenskapen vara en extern egenskap. Eftersom en extern egenskapsdeklaration inte tillhandahåller någon faktisk implementering ska var och en av de accessor_bodys i dess accessor_declarations vara semikolon.

15.7.2 Statiska egenskaper och instansegenskaper

När en egenskapsdeklaration innehåller en static modifierare sägs egenskapen vara en statisk egenskap. När det inte finns någon static modifierare sägs egenskapen vara en instansegenskap.

En statisk egenskap är inte associerad med en specifik instans, och det är ett kompileringsfel att referera till i åtkomsterna för this en statisk egenskap.

En instansegenskap är associerad med en viss instans av en klass och den instansen kan nås som this (§12.8.14) i egenskapens accessorer.

Skillnaderna mellan statiska medlemmar och instansmedlemmar diskuteras ytterligare i §15.3.8.

15.7.3 Åtkomstmetoder

Obs! Den här satsen gäller både egenskaper (§15.7) och indexerare (§15.9). Satsen är skriven i termer av egenskaper. Vid läsning för indexerare, ersätt indexerare/indexerare med egenskap/egenskaper och konsultera listan över skillnader mellan egenskaper och indexerare som finns i §15.9.2. slutkommentar

Accessor_declarations för en egenskap anger vilka körbara instruktioner som är kopplade till att skriva och/eller läsa egenskapen.

accessor_declarations
    : get_accessor_declaration set_accessor_declaration?
    | set_accessor_declaration get_accessor_declaration?
    ;

get_accessor_declaration
    : attributes? accessor_modifier? 'get' accessor_body
    ;

set_accessor_declaration
    : attributes? accessor_modifier? 'set' accessor_body
    ;

accessor_modifier
    : 'protected'
    | 'internal'
    | 'private'
    | 'protected' 'internal'
    | 'internal' 'protected'
    | 'protected' 'private'
    | 'private' 'protected'
    | 'readonly'        // direct struct members only
    ;

accessor_body
    : block
    | '=>' expression ';'
    | ';' 
    ;

ref_get_accessor_declaration
    : attributes? accessor_modifier? 'get' ref_accessor_body
    ;
    
ref_accessor_body
    : block
    | '=>' 'ref' variable_reference ';'
    | ';'
    ;

Accessor_declarations består av en get_accessor_declaration, en set_accessor_declaration eller båda. Varje åtkomstdeklaration består av valfria attribut, en valfri accessor_modifier, token get eller set, följt av en accessor_body.

För en referensvärdesegenskap består ref_get_accessor_declaration av valfria attribut, en valfri accessor_modifier, token get, följt av en ref_accessor_body.

Användningen av accessor_modifierstyrs av följande begränsningar:

  • En accessor_modifier får inte användas i ett gränssnitt eller i en explicit gränssnittsmedlemsimplementering.
  • accessor_modifier tillåts endast i en property_declaration eller indexer_declaration som direkt ingår i en struct_declaration (§16.4.11, §16.4.13).
  • För en egenskap eller indexerare som inte har någon override modifierare tillåts endast en accessor_modifier om egenskapen eller indexeraren har både en get- och set-åtkomstor och sedan endast tillåts på någon av dessa åtkomstorer.
  • För en egenskap eller indexerare som innehåller en override-modifierare ska en accessor matcha accessor_modifier, om möjligt, hos den accessor som åsidosätts.
  • Accessor_modifier ska deklarera en tillgänglighet som är strikt mer restriktiv än den deklarerade tillgängligheten för själva egendomen eller indexeraren. För att vara exakt:
    • Om egenskapen eller indexeraren har en deklarerad tillgänglighet för 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 egenskap ska ha minst en get-accessor. För accessorer till andra icke-abstrakta, icke-externa egenskaper är accessor_body antingen:

  • ett block som anger vilka instruktioner som ska köras när motsvarande accessor anropas, eller
  • en uttryckstext, som består av => följt av ett uttryck och ett semikolon, och anger ett enda uttryck som ska köras när motsvarande accessor anropas.

För abstract och extern referensvärdesegenskaper är ref_accessor_body helt enkelt ett semikolon. För en accessor för någon annan icke-abstrakt, icke-extern egenskap är ref_accessor_body antingen:

  • ett block som anger vilka instruktioner som ska köras när get-accessorn anropas, eller
  • en uttryckstext, som består av => följt 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ärdeegenskap ska överensstämma med reglerna för metoder som returnerar värden, som beskrivs i §15.6.11. I synnerhet ska alla return instruktioner i en get-accessor ange ett uttryck som implicit är konvertibelt till egenskapstypen. Dessutom ska slutpunkten för en get-accessor inte vara tillgänglig.

En get-accessor för en referensvärdesegenskap motsvarar en parameterlös metod med ett returvärde för en variable_reference till en variabel av egenskapstypen. När en sådan egenskap refereras i ett uttryck anropas dess get-accessor för att beräkna egenskapens variable_reference värde. Den variabelreferensen, precis som alla andra, används sedan för att läsa eller, för icke-skrivskyddade variabelreferenser, skriva den refererade variabeln enligt kontexten.

Exempel: I följande exempel visas en referensvärdesegenskap som mål för en tilldelning:

class Program
{
    static int field;
    static ref int Property => ref field;

    static void Main()
    {
        field = 10;
        Console.WriteLine(Property); // Prints 10
        Property = 20;               // This invokes the get accessor, then assigns
                                     // via the resulting variable reference
        Console.WriteLine(field);    // Prints 20
    }
}

slutexempel

Kroppen till en get-åtkomstmetod för en egenskap värderad som ref skall följa reglerna för ref-värderade metoder beskrivna i §15.6.11.

En set-accessor motsvarar en metod med en enda värdeparameter av egenskapens typ och en void returtyp. Den implicita parametern för en uppsättningsåtkomstor heter alltid value. När en egenskap refereras till som mål för en tilldelning (§12.21), eller som operande av eller ++ (–-, §12.9.6), anropas den inställda accessorn med ett argument som ger det nya värdet (§12.21.2). Kroppen av en set-tilldelare ska följa reglerna för void metoder som beskrivs i §15.6.11. I synnerhet får returneringssatser i set-accessorns metodkropp inte ange ett uttryck. Eftersom en uppsättningsåtkomst i sig har en parameter med namnet value, är det ett kompileringsfel för en lokal variabel- eller konstantdeklaration i en uppsättningsåtkomst att ha det namnet.

Baserat på närvaron eller frånvaron av get- och set-åtkomstmetoder, klassificeras en egenskap som följer:

  • En egenskap som innehåller både en get-accessor och en set-accessor sägs vara en läs- och skrivbar egenskap.
  • En egenskap som har enbart en get-accessor kallas för en skrivskyddad egenskap. Det är ett kompileringsfel att en skrivskyddad egenskap är målet för en tilldelning.
  • En egenskap som endast har en set-accessor kallas för en skrivskyddad egenskap. Förutom när det gäller målet för en tilldelning, är det ett kompileringsfel att referera till en skrivskyddad egenskap i ett uttryck.

Obs! Pre- och postfix ++ och -- operatorer och sammansatta tilldelningsoperatorer kan inte tillämpas på skrivbara egenskaper, eftersom dessa operatorer läser det gamla värdet för sina operander innan de skriver det nya. slutkommentar

Exempel: I följande kod

public class Button : Control
{
    private string caption;

    public string Caption
    {
        get => caption;
        set
        {
            if (caption != value)
            {
                caption = value;
                Repaint();
            }
        }
    }

    public override void Paint(Graphics g, Rectangle r)
    {
        // Painting code goes here
    }
}

kontrollen Button deklarerar en offentlig Caption egendom. Get-åtkomstmetoden för Caption-egenskapen returnerar string som lagras i det privata caption-fältet. Set-åtkomstmetoden kontrollerar om det nya värdet skiljer sig från det nuvarande värdet, och om så är fallet, lagrar det nya värdet och målar om kontrollen. Egenskaper följer ofta mönstret som visas ovan: Get-accessorn returnerar helt enkelt ett värde som lagras i 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

Get- och set-åtkomstmetoder för en egenskap är inte distinkta medlemmar, och det är inte möjligt att deklarera åtkomstmetoderna för en egenskap separat.

Exempel: Exemplet

class A
{
    private string name;

    // Error, duplicate member name
    public string Name
    { 
        get => name;
    }

    // Error, duplicate member name
    public string Name
    { 
        set => name = value;
    }
}

deklarerar inte en enda läs- och skrivbar egenskap. I stället deklareras två egenskaper med samma namn, en skrivskyddad och en skrivbar endast. Eftersom två medlemmar som deklareras i samma klass inte kan ha samma namn orsakar exemplet ett kompileringsfel.

slutexempel

När en härledd klass deklarerar en egenskap med samma namn som en ärvd egenskap döljer den härledda egenskapen den ärvda egenskapen med avseende på både läsning och skrivning.

Exempel: I följande kod

class A
{
    public int P
    {
        set {...}
    }
}

class B : A
{
    public new int P
    {
        get {...}
    }
}

egenskapen P i B döljer egenskapen P i A avseende på både läsning och skrivning. Således, 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 av b.P gör att ett kompileringsfel rapporteras, eftersom den skrivskyddade P-egenskapen i B döljer den skrivbara P-egenskapen i A. Observera dock att en omvandling 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- och Y-egenskap och som en Location-egenskap av typen Point. Om det i en framtida version av Label blir enklare att lagra platsen som en Point internt kan ändringen göras utan att det offentliga gränssnittet för klassen påverkas:

class Label
{
    private Point location;
    private string caption;

    public Label(int x, int y, string caption)
    {
        this.location = new Point(x, y);
        this.caption = caption;
    }

    public int X => location.X;
    public int Y => location.Y;
    public Point Location => location;
    public string Caption => caption;
}

Om x 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 är motsvarande att läsa värdet av ett fält när man anropar en get-accessor, anses det vara dålig programmeringsstil att get-acccessorer har observerbara bieffekter. I exemplet

class Counter
{
    private int next;

    public int Next => next++;
}

värdet för Next egenskapen beror på hur många gånger egenskapen har använts tidigare. Därför ger åtkomst till egenskapen en observerbar bieffekt, och egenskapen bör implementeras som en metod i stället.

Konventionen "inga biverkningar" för get-accessorer innebär inte att get-accessorer bör skrivas endast för att returnera värden som lagras i fält. Get-användare beräknar ofta värdet för en egenskap genom att komma åt flera fält eller anropa metoder. Men en korrekt utformad get-accessor utför inga åtgärder som orsakar observerbara ändringar i objektets tillstånd.

slutexempel

Egenskaper kan användas för att fördröja initieringen av en resurs tills den först refereras.

Exempel:

public class Console
{
    private static TextReader reader;
    private static TextWriter writer;
    private static TextWriter error;

    public static TextReader In
    {
        get
        {
            if (reader == null)
            {
                reader = new StreamReader(Console.OpenStandardInput());
            }
            return reader;
        }
    }

    public static TextWriter Out
    {
        get
        {
            if (writer == null)
            {
                writer = new StreamWriter(Console.OpenStandardOutput());
            }
            return writer;
        }
    }

    public static TextWriter Error
    {
        get
        {
            if (error == null)
            {
                error = new StreamWriter(Console.OpenStandardError());
            }
            return error;
        }
    }
...
}

Klassen Console innehåller tre egenskaper, In, 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 till Out-egenskapen, som i

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

den underliggande TextWriter för utgångsenheten 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ärde egenskap med enbart semikolon i accessor_body. Autoegenskaper ska ha en get-accessor och kan eventuellt ha en set-accessor.

När en egenskap anges som en automatiskt implementerad egenskap är ett dolt bakgrundsfält automatiskt tillgängligt för egenskapen och åtkomsterna implementeras för att läsa från och skriva till det bakgrundsfältet. Det dolda bakgrundsfältet är otillgängligt, det kan bara läsas och skrivas via de automatiskt implementerade egenskapsåtkomsterna, även inom den innehållande typen. Om den automatiska egenskapen inte har någon set-tillgång, betraktas bakgrundsfältet som readonly (§15.5.3). Precis som ett readonly fält kan en skrivskyddad automatiskt egenskap också tilldelas inuti kroppen av en konstruktör i den omslutande klassen. En sådan tilldelning tilldelar direkt till fältet för skrivskyddad stöd av egenskapen.

En automatisk egenskap kan valfritt ha en property_initializer, vilket tillämpas direkt på bakåtkompatibla fältet som en variable_initializer (§17.7).

Exempel:

public class Point
{
    public int X { get; set; } // Automatically implemented
    public int Y { get; set; } // Automatically implemented
}

motsvarar följande deklaration:

public class Point
{
    private int x;
    private int y;

    public int X { get { return x; } set { x = value; } }
    public int Y { get { return y; } set { y = value; } }
}

slutexempel

Exempel: I följande

public class ReadOnlyPoint
{
    public int X { get; }
    public int Y { get; }

    public ReadOnlyPoint(int x, int y)
    {
        X = x;
        Y = y;
    }
}

motsvarar följande deklaration:

public class ReadOnlyPoint
{
    private readonly int __x;
    private readonly int __y;
    public int X { get { return __x; } }
    public int Y { get { return __y; } }

    public ReadOnlyPoint(int x, int y)
    {
        __x = x;
        __y = y;
    }
}

Tilldelningarna till det skrivskyddade fältet är giltiga eftersom de sker inom konstruktorn.

slutexempel

Även om bakgrundsfältet är dolt kan fältet ha fältriktade attribut som tillämpas direkt på det via den automatiskt implementerade egenskapens property_declaration (§15.7.1).

Exempel: Följande kod

[Serializable]
public class Foo
{
    [field: NonSerialized]
    public string MySecret { get; set; }
}

resulterar i det fältriktade attribut NonSerialized som tillämpas på det kompilatorgenererade bakgrundsfältet, som om koden hade skrivits på följande sätt:

[Serializable]
public class Foo
{
    [NonSerialized]
    private string _mySecretBackingField;
    public string MySecret
    {
        get { return _mySecretBackingField; }
        set { _mySecretBackingField = value; }
    }
}

slutexempel

15.7.5 Hjälpmedel

Om en accessor har en accessor_modifier bestäms accessorns tillgänglighetsdomän (§7.5.3) med hjälp av den deklarerade tillgängligheten för accessor_modifier. Om en accessor inte har någon accessor_modifier bestäms tillgänglighetsdomänen för accessorn från den deklarerade tillgängligheten för egenskapen eller indexeraren.

Förekomsten av en accessor_modifier påverkar aldrig medlemsuppslag (§12.5) eller överlagringslösning (§12.6.4). Modifierarna på egenskapen eller indexeraren avgör alltid vilken egenskap eller indexerare som är bunden till, oavsett kontexten för åtkomsten.

När en viss icke-referensvärdesegenskap eller icke-referensvärdesindexerare har valts används tillgänglighetsdomänerna för de specifika berörda åtkomsterna för att avgöra om användningen är giltig:

  • Om användningen är som ett värde (§12.2.2), ska get-accessorn finnas och vara tillgänglig.
  • Om användningen är målet för en enkel tilldelning (§12.21.2), ska den angivna accessorn finnas och vara tillgänglig.
  • Om användningen är som målet för sammansatt tilldelning (§12.21.4), eller som målet för ++- eller ---operatörerna (§12.8.16, §12.9.6), ska både get-accessorerna och set-accessorn finnas och vara tillgängliga.

Exempel: I följande exempel döljs egenskapen A.Text av 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 – används tillgänglighetsdomänen för den berörda get-accessorn för att avgöra om användningen är giltig.

En accessor som används för att implementera ett gränssnitt får inte ha någon accessor_modifier. Om endast en accessor används för att implementera ett gränssnitt kan den andra accessorn deklareras med en accessor_modifier:

Exempel:

public interface I
{
    string Prop { get; }
}

public class C : I
{
    public string Prop
    {
        get => "April";     // Must not have a modifier here
        internal set {...}  // Ok, because I.Prop has no set accessor
    }
}

slutexempel

15.7.6 Virtuella, förseglade, åsidosatta och abstrakta accessorer

Obs! Den här satsen gäller både egenskaper (§15.7) och indexerare (§15.9). Satsen är skriven i termer av egenskaper. Vid läsning för indexerare, ersätt indexerare/indexerare med egenskap/egenskaper och konsultera listan över skillnader mellan egenskaper och indexerare som finns i §15.9.2. slutkommentar

En virtuell egenskapsdeklaration anger att åtkomstmetoderna för egenskapen är virtuella. virtual-modifieraren tillämpas på alla icke-privata åtkomstmetoder hos en egenskap. När en accessor för en virtuell egenskap har privateaccessor_modifier, är åtkomsten till den privata egenskapen implicit inte virtuell.

En abstrakt egenskapsdeklaration anger att accessorerna för egenskapen är virtuella, men tillhandahåller inte någon faktisk implementering av dessa. I stället krävs det att icke-abstrakta härledda klasser tillhandahåller sin egen implementering för accessorerna genom att åsidosätta egenskapen. Eftersom en accessor för en abstrakt egenskapsdeklaration inte tillhandahåller någon faktisk implementering består dess accessor_body helt enkelt av ett semikolon. En abstrakt egenskap får inte ha någon private accessor.

En egenskapsdeklaration som innehåller både abstract modifierare och override anger att egenskapen är abstrakt och åsidosätter en basegenskap. Åtkomstmetoderna för en sådan egenskap är också abstrakta.

Abstrakta egenskapsdeklarationer tillåts endast i abstrakta klasser (§15.2.2.2). Åtkomstmetoderna för en ärvd virtuell egenskap kan åsidosättas i en efterföljande klass genom att inkludera en egenskapsdeklaration som specificerar en override direktiv. Detta kallas för en åsidosättande egenskapsdeklaration. En åsidosättande egenskapsdeklaration deklarerar inte en ny egenskap. Istället specialiserar den helt enkelt implementeringarna av åtkomstmetoderna för en befintlig virtuell egenskap.

Åsidosättningsdeklarationen och den åsidosatta basegenskapen måste ha samma deklarerade åtkomstnivå. Med andra ord, en åsidosättningsdeklaration får inte ändra åtkomsten av baskomponenten. Men om den åsidosatta basegenskapen är skyddad internt och den deklareras i en annan sammansättning än den sammansättning som innehåller åsidosättningsdeklarationen ska åsidosättningsdeklarationens deklarerade tillgänglighet skyddas. Om den ärvda egenskapen bara har en enda accessor (dvs. om den ärvda egenskapen är skrivskyddad eller skrivbaserad) ska den åsidosättande egenskapen endast innehålla den accessorn. Om den ärvda egenskapen innehåller båda accessorerna (dvs. om den ärvda egenskapen är både läsbar och skrivbar) kan den överordnade egenskapen innehålla antingen en enskild accessor eller båda accessorerna. Det ska ske en identitetskonvertering mellan den överskridande och den ärvda egenskapens typ.

En överskrivande egenskapsdeklaration kan innehålla modifieraren sealed. Användning av den här modifieraren förhindrar att en härledd klass ytterligare åsidosättar egenskapen. Åtkomstmetoderna för en förseglad egenskap är också förseglade.

Förutom skillnader i deklarations- och anropssyntax fungerar virtuella, förseglade, åsidosättnings- och abstrakta accessorer exakt som virtuella, förseglade, åsidosättnings- och abstrakta metoder. Närmare bestämt gäller de regler som beskrivs i §15.6.4, §15.6.5, §15.6.6 och §15.6.7 som om accessorer vore metoder av motsvarande form:

  • En get-accessor motsvarar en parameterlös metod med ett returvärde av egenskapstypen och samma modifierare som den innehållande egenskapen.
  • En set-accessor motsvarar en metod med en enda värdeparameter av egenskapens typ, en void-returtyp och samma modifierare som den omslutande egenskapen.

Exempel: I följande kod

abstract class A
{
    int y;

    public virtual int X
    {
        get => 0;
    }

    public virtual int Y
    {
        get => y;
        set => y = value;
    }

    public abstract int Z { get; set; }
}

X är en virtuell skrivskyddad egenskap, Y är en virtuell läs- och skrivbar egenskap, och Z är en abstrakt läs- och skrivbar egenskap. Eftersom Z är abstrakt ska den innehållande klassen A också deklareras abstrakt.

En klass som härleds från A visas nedan:

class B : A
{
    int z;

    public override int X
    {
        get => base.X + 1;
    }

    public override int Y
    {
        set => base.Y = value < 0 ? 0: value;
    }

    public override int Z
    {
        get => z;
        set => z = value;
    }
}

Här är deklarationerna för X, Y och Z överordnande egenskapsdeklarationer. Varje egenskapsdeklaration överensstämmer exakt med åtkomstmodifierarna, typen och namnet på motsvarande ärvda egenskap. Get-accessorn av X och set-accessorn av Y använder 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 override, ska alla åsidosatta accessorer vara tillgängliga för den åsidosättande koden. Dessutom ska den deklarerade tillgängligheten för både själva egenskapen eller indexeraren, och för åtkomstgivarna, överensstämma med den åsidosatta medlemmens och åtkomstgivarnas.

Exempel:

public class B
{
    public virtual int P
    {
        get {...}
        protected set {...}
    }
}

public class D: B
{
    public override int P
    {
        get {...}            // Must not have a modifier here
        protected set {...}  // Must specify protected here
    }
}

slutexempel

15.8 Händelser

15.8.1 Allmänt

En händelse är en medlem som gör det möjligt för ett objekt eller en klass att tillhandahålla meddelanden. Klienter kan koppla körbar kod för händelser genom att tillhandahålla händelsehanterare.

Händelser deklareras med event_declaration s:

event_declaration
    : attributes? event_modifier* 'event' type variable_declarators ';'
    | attributes? event_modifier* 'event' type member_name
        '{' event_accessor_declarations '}'
    ;

event_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'static'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | 'readonly'        // direct struct members only
    | unsafe_modifier   // unsafe code support
    ;

event_accessor_declarations
    : add_accessor_declaration remove_accessor_declaration
    | remove_accessor_declaration add_accessor_declaration
    ;

add_accessor_declaration
    : attributes? 'add' block
    ;

remove_accessor_declaration
    : attributes? 'remove' block
    ;

unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).

En event_declaration kan innehålla en uppsättning attribut (§22) och någon av de tillåtna typerna av deklarerad tillgänglighet (§15.3.6), new (§15.3.5), static (§15.6.3, §15.8.4), virtual (§15.6.4, §15.8.5), override (§15.6.5, §15.8.5), sealed (§15.6.6), abstract (§15.6.7, §15.8.5) och extern (§15.6.8) modifierare. Dessutom kan en event_declaration som finns direkt av en struct_declaration inkludera readonly modifieraren (§16.4.12).

Händelsedeklarationer omfattas av samma regler som metoddeklarationer (§15.6) när det gäller giltiga kombinationer av modifierare.

Typ av händelsedeklaration ska vara en delegate_type (§8.2.8), och den delegate_type ska vara minst lika tillgänglig som själva händelsen (§7.5.5).

En händelsedeklaration kan innehålla event_accessor_declarations. Men om den inte gör det, för icke-externa, icke-abstrakta händelser, ska en kompilator tillhandahålla dem automatiskt (§15.8.2); för extern händelser tillhandahålls åtkomsterna externt.

En händelsedeklaration som utelämnar event_accessor_declarations definierar en eller flera händelser – en för var och en av de variable_declarator. Attributen och modifierarna gäller för alla medlemmar som deklareras av en sådan event_declaration.

Det är ett kompileringsfel för en event_declaration att inkludera både abstract modifieraren och event_accessor_declarations.

När en händelsedeklaration innehåller en extern modifierare sägs händelsen vara en extern händelse. Eftersom en extern händelsedeklaration inte innehåller någon faktisk implementering, är det ett misstag att inkludera både extern-modifieraren och event_accessor_declarations.

Det är ett kompileringstidsfel för en variabeldeklarator i en händelsedeklaration med en abstract eller external modifierare att inkludera en variabelinitierare.

En händelse kan användas som den vänstra operanden för operatorerna += och -= . Dessa operatorer används för att koppla händelsehanterare till eller för att ta bort händelsehanterare från en händelse, och åtkomstmodifierarna för händelsen kontrollerar de kontexter där sådana åtgärder tillåts.

De enda åtgärder som tillåts för en händelse med kod som ligger utanför den typ där händelsen deklareras är += och -=. Även om sådan kod kan lägga till och ta bort hanterare för en händelse kan den därför inte hämta eller ändra den underliggande listan över händelsehanterare direkt.

Vid en operation av formen x += y eller x –= y har resultatet typen x när void är en händelse (§12.21.5), till skillnad från att ha typen av x med värdet x efter tilldelningen, vilket är fallet för andra += och -= operatorer som definieras på icke-händelsetyper. Detta förhindrar att extern kod indirekt undersöker den underliggande delegaten för en händelse.

Exempel: I följande exempel visas hur händelsehanterare är kopplade till instanser av Button klassen:

public delegate void EventHandler(object sender, EventArgs e);

public class Button : Control
{
    public event EventHandler Click;
}

public class LoginDialog : Form
{
    Button okButton;
    Button cancelButton;

    public LoginDialog()
    {
        okButton = new Button(...);
        okButton.Click += new EventHandler(OkButtonClick);
        cancelButton = new Button(...);
        cancelButton.Click += new EventHandler(CancelButtonClick);
    }

    void OkButtonClick(object sender, EventArgs e)
    {
        // Handle okButton.Click event
    }

    void CancelButtonClick(object sender, EventArgs e)
    {
        // Handle cancelButton.Click event
    }
}

LoginDialog Här skapar instanskonstruktorn två Button instanser och kopplar händelsehanterare 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 delegeringsanropet föregås av en kontroll som säkerställer att delegeringen inte är null och att kontrollen görs på en lokal kopia för att säkerställa trådsäkerhet.

Utanför klassens 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 i formen:

class X
{
    public event D Ev;
}

ska sammanställas till någonting som motsvarar

class X
{
    private D __Ev; // field to hold the delegate

    public event D Ev
    {
        add
        {
            /* Add the delegate in a thread safe way */
        }
        remove
        {
            /* Remove the delegate in a thread safe way */
        }
    }
}

I klassen X leder referenser till Ev på den vänstra sidan av +=- och –=-operatorerna till att tilläggs- och borttagningsaccessorerna 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 Event-åtkomstmetoder

Obs! Händelsedeklarationer utelämnar vanligtvis event_accessor_declarations, som i exemplet Button ovan. De kan till exempel inkluderas om lagringskostnaden för ett fält per händelse inte är acceptabel. I sådana fall kan en klass inkludera event_accessor_declarations och använda en privat mekanism för att lagra listan över händelsehanterare. slutkommentar

I event_accessor_declarations för en händelse anger du de körbara instruktioner som är associerade med att lägga till och ta bort händelsehanterare.

Åtkomstdeklarationerna består av en lägg_till_åtkomstdeklaration och en ta_bort_åtkomstdeklaration. Varje åtkomstdeklaration består av antingen lägga till- eller ta bort-token följt av ett block. Blocket som är associerat med en add_accessor_declaration anger de instruktioner som ska köras när en händelsehanterare läggs till, och blocket som är associerat med en remove_accessor_declaration anger de instruktioner som ska köras när en händelsehanterare tas bort.

Varje add_accessor_declaration och remove_accessor_declaration motsvarar en metod med en enskild värdeparameter av händelsetypen och en void returtyp. Den implicita parametern för en händelseåtkomstor heter value. När en händelse används i en händelsehantering används lämplig åtkomstmetod. Mer specifikt, om tilldelningsoperatorn är += används lägg till-accessorn, och om tilldelningsoperatorn är –= används ta bort-accessorn. I båda fallen används högeroperanden för tilldelningsoperatorn som argument till händelse-åtkomstmetoden. Blocket för en add_accessor_declaration eller en remove_accessor_declaration skall överensstämma med de regler för void metoder som beskrivs i §15.6.9. I synnerhet får satser i return i ett sådant block inte ange ett uttryck.

Eftersom en händelseåtkomst implicit har en parameter med namnet value, är det ett kompileringsfel för en lokal variabel eller konstant som deklarerats i en händelseåtkomst att ha det namnet.

Exempel: I följande kod


class Control : Component
{
    // Unique keys for events
    static readonly object mouseDownEventKey = new object();
    static readonly object mouseUpEventKey = new object();

    // Return event handler associated with key
    protected Delegate GetEventHandler(object key) {...}

    // Add event handler associated with key
    protected void AddEventHandler(object key, Delegate handler) {...}

    // Remove event handler associated with key
    protected void RemoveEventHandler(object key, Delegate handler) {...}

    // MouseDown event
    public event MouseEventHandler MouseDown
    {
        add { AddEventHandler(mouseDownEventKey, value); }
        remove { RemoveEventHandler(mouseDownEventKey, value); }
    }

    // MouseUp event
    public event MouseEventHandler MouseUp
    {
        add { AddEventHandler(mouseUpEventKey, value); }
        remove { RemoveEventHandler(mouseUpEventKey, value); }
    }

    // Invoke the MouseUp event
    protected void OnMouseUp(MouseEventArgs args)
    {
        MouseEventHandler handler;
        handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey);
        if (handler != null)
        {
            handler(this, args);
        }
    }
}

Control-klassen implementerar en intern lagringsmekanism för händelser. 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 för en statisk händelses åtkomstfunktioner.

En händelseinstans är associerad med en given instans av en klass, och denna instans kan nås som this (§12.8.14) i händelsens accessorer.

Skillnaderna mellan statiska medlemmar och instansmedlemmar diskuteras ytterligare i §15.3.8.

15.8.5 Virtuella, förseglade, åsidosättnings- och abstrakta accessorer

En virtuell händelsedeklaration anger att åtkomsten till den händelsen är virtuell. Modifieraren virtual gäller för båda åtkomstpunkterna för en händelse.

En abstrakt händelsedeklaration anger att åtkomsten till händelsen är virtuell, men ger ingen faktisk implementering av åtkomsterna. I stället måste icke-abstrakta härledda klasser tillhandahålla sin egen implementering för accessorerna genom att åsidosätta händelsen. Eftersom en accessor för en abstrakt händelsedeklaration inte tillhandahåller något faktiskt genomförande ska den inte tillhandahålla event_accessor_declarations.

En händelsedeklaration som innehåller både abstract modifierarna och override anger att händelsen är abstrakt och åsidosätter en bashändelse. Accessorerna för en sådan händelse är också abstrakta.

Abstrakta händelsedeklarationer tillåts endast i abstrakta klasser (§15.2.2.2).

Åtkomst till en ärvd virtuell händelse kan åsidosättas i en härledd klass genom att inkludera en händelsedeklaration som anger override-modifieraren. Detta kallas för en övergripande händelsedeklaration. En åsidosättande händelsedeklaration deklarerar inte en ny händelse. I stället specialiserar den sig helt enkelt på implementeringarna av accessorerna för en befintlig virtuell händelse.

En åsidosättande händelsedeklaration ska ange exakt samma åtkomstmodifierare och namn som den åsidosatta händelsen, det ska finnas en identitetsomvandling mellan typen av den åsidosättande och den åsidosatta händelsen, och både tilläggs- och borttagningsaccessorerna ska anges i deklarationen.

En övergripande händelsedeklaration kan innehålla sealed modifieraren. Användning av this modifierare hindrar en härledd klass från att åsidosätta händelsen ytterligare. Åtkomstmetoderna för en förseglad händelse är också förseglade.

Det är ett kompileringsfel för en övergripande händelsedeklaration som ska innehålla en new modifierare.

Förutom skillnader i deklarations- och anropssyntax fungerar virtuella, förseglade, åsidosättnings- och abstrakta accessorer exakt som virtuella, förseglade, åsidosättnings- och abstrakta metoder. Närmare bestämt gäller de regler som beskrivs i §15.6.4, §15.6.5, §15.6.6 och §15.6.7 som om accessorer vore metoder av motsvarande form. Varje accessor motsvarar en metod med en enskild värdeparameter av händelsetypen, en void returtyp och samma modifierare som den innehållande händelsen.

15,9 indexerare

15.9.1 Allmänt

En indexerare är en medlem som gör att ett objekt kan indexeras på samma sätt som en matris. Indexerare deklareras med indexer_declaration s:

indexer_declaration
    : attributes? indexer_modifier* indexer_declarator indexer_body
    | attributes? indexer_modifier* ref_kind indexer_declarator ref_indexer_body
    ;

indexer_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'virtual'
    | 'sealed'
    | 'override'
    | 'abstract'
    | 'extern'
    | 'readonly'        // direct struct members only
    | unsafe_modifier   // unsafe code support
    ;

indexer_declarator
    : type 'this' '[' parameter_list ']'
    | type interface_type '.' 'this' '[' parameter_list ']'
    ;

indexer_body
    : '{' accessor_declarations '}' 
    | '=>' expression ';'
    ;  

ref_indexer_body
    : '{' ref_get_accessor_declaration '}'
    | '=>' 'ref' variable_reference ';'
    ;

unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).

En indexer_declaration kan innehålla en uppsättning attribut (§22) och någon av de tillåtna typerna av deklarerad tillgänglighet (§15.3.6), new (§15.3.5), virtual (§15.15.1 6.4), override (§15.6.5), sealed (§15.6.6), abstract (§15.6.7) och extern (§15.6.8) modifierare. Dessutom kan en indexer_declaration som finns direkt av en struct_declaration inkludera readonly modifieraren (§16.4.12).

  • Den första deklarerar en icke-referensvärdesindexerare. Dess värde har typen type. Den här typen av indexerare kan vara läsbar och/eller skrivbar.
  • Den andra deklarerar en referensvärdesindexerare. Dess värde är en variabelreferens (§9.5), som kan vara readonly, till en variabel av typen typ. Den här typen av indexerare är bara läsbar.

En indexer_declaration kan innehålla en uppsättning attribut (§22) och någon av de tillåtna typerna av deklarerad tillgänglighet (§15.3.6), new (§15.3.5), virtual (§15.15.) 6.4), override (§15.6.5), sealed (§15.6.6), abstract (§15.6.7) och extern (§15.6.8) modifierare.

Indexerarens deklarationer omfattas av samma regler som metoddeklarationer (§15.6) när det gäller giltiga kombinationer av modifierare, med det enda undantaget att modifieraren inte är tillåten static i en indexerardeklaration.

Typen av indexeraredeklaration anger elementtypen för indexeraren som introducerades av deklarationen.

Obs! Eftersom indexerare är utformade för att användas i matriselementliknande kontexter används även termelementtypensom definierats för en matris med en indexerare. slutkommentar

Om inte indexeraren är en explicit implementering av en gränssnittsmedlem, följs typen av nyckelordet this. För en explicit implementation av ett gränssnittselement följs typen av ett gränssnittstyp, ett "." och nyckelordet this. Till skillnad från andra medlemmar har indexerare inte användardefinierade namn.

Parameter_list anger indexerarens parametrar. Parameterlistan för en indexerare motsvarar en metod (§15.6.2), förutom att minst en parameter ska anges och att 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 en uttalandekropp, accessor_declarations, som ska omges av "{" och "}" token, deklarerar åtkomsterna hos indexeraren (§15.7.3). Åtkomstmetoderna anger de körbara instruktioner som är associerade med att läsa och skriva indexerarelement.

I en indexer_body en uttryckstext som består av "=>" följt av ett uttryck E och ett semikolon är exakt likvärdigt med uttryckstexten { get { return E; } }och kan därför endast användas för att ange skrivskyddade indexerare där resultatet av get-accessorn ges av ett enda uttryck.

En ref_indexer_body kan antingen bestå av en satskropp eller en uttryckskropp. I ett satsblock deklarerar get_accessor_declaration get-accessorn (§15.7.3) för indexeraren. Accessorn anger de körbara instruktioner som är associerade med att läsa indexeraren.

I en ref_indexer_body är en expressionskropp bestående av => följt av ref, en variable_referenceV och ett semikolon exakt motsvarigheten till uttryckskroppen { get { return ref V; } }.

Obs! Även om syntaxen för att komma åt ett indexerarelement är densamma som för ett matriselement klassificeras inte ett indexerarelement som en variabel. Därför är det inte möjligt att passera ett indexerarelement som ett in, out eller ref argument om inte indexeraren är referensvärde och därför returnerar en referens (§9.7). slutkommentar

Indexerarens parameter_list definierar indexerarens signatur (§7.6). Mer specifikt består signaturen för en indexerare av antalet och typerna av dess parametrar. Elementtypen och namnen på parametrarna ingår inte i en indexerares signatur.

En indexerares signatur ska skilja sig från signaturerna för alla andra indexerare som deklarerats i samma klass.

När en indexeraredeklaration innehåller en extern modifierare sägs indexeraren vara en extern indexerare. Eftersom en extern indexerardeklaration inte ger någon faktisk implementering ska var och en av de accessor_bodyi sin accessor_declarations vara semikolon.

Exempel: Exemplet nedan deklarerar en BitArray klass som implementerar en indexerare för åtkomst till enskilda bitar i bitmatrisen.

class BitArray
{
    int[] bits;
    int length;

    public BitArray(int length)
    {
        if (length < 0)
        {
            throw new ArgumentException();
        }
        bits = new int[((length - 1) >> 5) + 1];
        this.length = length;
    }

    public int Length => length;

    public bool this[int index]
    {
        get
        {
            if (index < 0 || index >= length)
            {
                throw new IndexOutOfRangeException();
            }
            return (bits[index >> 5] & 1 << index) != 0;
        }
        set
        {
            if (index < 0 || index >= length)
            {
                throw new IndexOutOfRangeException();
            }
            if (value)
            {
                bits[index >> 5] |= 1 << index;
            }
            else
            {
                bits[index >> 5] &= ~(1 << index);
            }
        }
    }
}

En instans av BitArray klassen förbrukar betydligt mindre minne än en 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[].

Följande CountPrimes klass använder en BitArray och den klassiska "sieve-algoritmen" för att beräkna antalet primtal mellan 2 och ett givet maximum:

class CountPrimes
{
    static int Count(int max)
    {
        BitArray flags = new BitArray(max + 1);
        int count = 0;
        for (int i = 2; i <= max; i++)
        {
            if (!flags[i])
            {
                for (int j = i * 2; j <= max; j += i)
                {
                    flags[j] = true;
                }
                count++;
            }
        }
        return count;
    }

    static void Main(string[] args)
    {
        int max = int.Parse(args[0]);
        int count = Count(max);
        Console.WriteLine($"Found {count} primes between 2 and {max}");
    }
}

Observera att syntaxen för att komma åt element i BitArray är exakt samma som för en bool[].

I följande exempel visas en 26×10-rutnätsklass som har en indexerare med två parametrar. Första parametern måste vara en bokstav från A till Z, och den andra parametern måste vara ett heltal från 0 till 9.

class Grid
{
    const int NumRows = 26;
    const int NumCols = 10;
    int[,] cells = new int[NumRows, NumCols];

    public int this[char row, int col]
    {
        get
        {
            row = Char.ToUpper(row);
            if (row < 'A' || row > 'Z')
            {
                throw new ArgumentOutOfRangeException("row");
            }
            if (col < 0 || col >= NumCols)
            {
                throw new ArgumentOutOfRangeException ("col");
            }
            return cells[row - 'A', col];
        }
        set
        {
            row = Char.ToUpper(row);
            if (row < 'A' || row > 'Z')
            {
                throw new ArgumentOutOfRangeException ("row");
            }
            if (col < 0 || col >= NumCols)
            {
                throw new ArgumentOutOfRangeException ("col");
            }
            cells[row - 'A', col] = value;
        }
    }
}

slutexempel

15.9.2 Indexerare och egenskapsskillnader

Indexerare och egenskaper är mycket lika begrepp, men skiljer sig åt på följande sätt:

  • En egenskap identifieras med dess namn, medan en indexerare identifieras med dess signatur.
  • En egenskap nås via en simple_name (§12.8.4) eller en member_access (§12.8.7), medan ett indexerarelement nås via en element_access (§12.8.12.3).
  • En egenskap kan vara en statisk medlem, medan en indexerare alltid är instansmedlem.
  • En get-accessor för en egenskap motsvarar en metod utan parametrar, medan en get-accessor för en indexerare motsvarar en metod med samma parameterlista som indexeraren.
  • En uppsättningsåtkomst för en egenskap motsvarar en metod med en enda parameter med namnet value, medan en uppsättningsåtkomstor för en indexerare motsvarar en metod med samma parameterlista som indexeraren, plus en ytterligare parameter med namnet value.
  • Det är ett kompileringsfel för en indexerarmetod att deklarera en lokal variabel eller lokal konstant med samma namn som en parameter för indexeraren.
  • I en överskrivande egenskapsdeklaration åtkommer man den ärvda egenskapen med syntaxen base.P, dä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 egenskap/egenskaper med indexerare vid läsning av §15.7.3, §15.7.5 och §15.7.6 gäller även för definierade termer. Specifikt blir läs- och skriv egenskapläs- och skriv indexerare, endast läs egenskap blir endast läs indexerare, och endast skriv egenskap blir endast skriv indexerare.

15.10 Operatörer

15.10.1 Allmänt

En operator är en medlem som definierar innebörden av en uttrycksoperator som kan tillämpas på instanser av klassen. Operatorer deklareras med operator_declaration s:

operator_declaration
    : attributes? operator_modifier+ operator_declarator operator_body
    ;

operator_modifier
    : 'public'
    | 'static'
    | 'extern'
    | unsafe_modifier   // unsafe code support
    ;

operator_declarator
    : unary_operator_declarator
    | binary_operator_declarator
    | conversion_operator_declarator
    ;

unary_operator_declarator
    : type 'operator' overloadable_unary_operator '(' fixed_parameter ')'
    ;

logical_negation_operator
    : '!'
    ;

overloadable_unary_operator
    : '+' | '-' | logical_negation_operator | '~' | '++' | '--' | 'true' | 'false'
    ;

binary_operator_declarator
    : type 'operator' overloadable_binary_operator
        '(' fixed_parameter ',' fixed_parameter ')'
    ;

overloadable_binary_operator
    : '+'  | '-'  | '*'  | '/'  | '%'  | '&' | '|' | '^'  | '<<' 
    | right_shift | '==' | '!=' | '>' | '<' | '>=' | '<='
    ;

conversion_operator_declarator
    : 'implicit' 'operator' type '(' fixed_parameter ')'
    | 'explicit' 'operator' type '(' fixed_parameter ')'
    ;

operator_body
    : block
    | '=>' expression ';'
    | ';'
    ;

unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).

Obs! Prefixets logiska negation (§12.9.4) och postfixets null-förlåtande operatorer (§12.8.9), medan de representeras av samma lexikala token (!), är distinkta. Den senare är inte en överladdningsbar operator. slutkommentar

Det finns tre kategorier av överbelastningsbara operatorer: Unary-operatorer (§15.10.2), binära operatorer (§15.10.3) och konverteringsoperatorer (§15.10.4).

Operator_body är antingen ett semikolon, en blockkropp (§15.6.1) eller en uttryckstext (§15.6.1). En blocktext består av ett block som anger vilka instruktioner som ska köras när operatorn anropas. Blocket ska överensstämma med reglerna för värdereturmetoder som beskrivs i §15.6.11. En uttryckstext består av => följt av ett uttryck och ett semikolon och anger ett enda uttryck som ska utföras när operatorn anropas.

För extern operatorer består operator_body helt enkelt av ett semikolon. För alla andra operatorer är operator_body antingen en blocktext eller en uttryckstext.

Följande regler gäller för alla operatordeklarationer:

  • En operatörsdeklaration ska innehålla både en public och en static modifierare.
  • Operatorns parametrar får inga andra modifierare än in.
  • En operatörs underskrift (§15.10.2, §15.10.3, §15.10.4) skall skilja sig från signaturerna för alla andra aktörer som deklarerats i samma klass.
  • Alla typer som anges i en operatörsdeklaration ska vara minst lika tillgängliga som operatören själv (§7.5.5).
  • Det är ett fel att samma modifierare visas flera gånger i en operatordeklaration.

Varje operatorkategori inför ytterligare begränsningar, enligt beskrivningen i följande underklasuler.

Precis som andra medlemmar ärvs operatorer som deklareras i en basklass av härledda klasser. Eftersom operatörsdeklarationer alltid kräver klassen eller structen där operatorn deklareras delta i operatorns signatur, är det inte möjligt för en operator som deklarerats i en härledd klass att dölja en operator som deklarerats i en basklass. Modifieraren new krävs alltså aldrig, och tillåts därför aldrig, i en operatörsdeklaration.

Ytterligare information om unary och binära operatorer finns i §12.4.

Ytterligare information om konverteringsoperatörer finns i §10.5.

15.10.2 Unary operator

Följande regler gäller för unary-operatordeklarationer, där T anger instanstypen för klassen eller structen som innehåller operatordeklarationen:

  • En unary +, -, ! (endast logisk negation) eller ~ operator ska ta en enda parameter av 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 operand direkt eftersom detta skulle strida mot standardsemantiken för postfixstegsoperatorn. (§12.8.16)

slutexempel

15.10.3 Binära operatorer

Följande regler gäller för deklarationer av binära operatorer, där T anger instanstypen för klassen eller structen som innehåller operatordeklarationen:

  • En binär icke-skiftoperator ska ha två parametrar, varav minst en ska ha typ T eller T?, och kan returnera vilken typ som helst.
  • En binär << eller >> operator (§12.11) ska ha två parametrar, varav den första ska ha typ T eller T? och den andra av vilka ska ha typ int eller int?, och kan returnera vilken typ som helst.

Signaturen för en binär operator består av operatortoken (+, , -*, /, %, &, |^, , <<, >>, ==, !=>, <, >=, eller <=) och typerna av de två parametrarna. Returtypen och namnen på parametrarna ingår inte i en binär operators signatur.

Vissa binära operatorer kräver parvis deklaration. För varje deklaration av en operatör av ett par ska det finnas en matchande deklaration av den andra operatören i paret. Två operatordeklarationer överensstämmer om det finns identitetskonverteringar mellan deras returtyper och motsvarande parametertyper. Följande operatorer kräver parvis deklaration:

  • operator == och operator !=
  • operator > och operator <
  • operator >= och operator <=

15.10.4 Konverteringsoperatorer

En konverteringsoperatordeklaration introducerar en användardefinierad konvertering (§10.5), som utökar de fördefinierade implicita och explicita konverteringarna.

En konverteringsoperatordeklaration som innehåller nyckelordet implicit introducerar en användardefinierad implicit konvertering. Implicita omvandlingar kan inträffa i en mängd olika situationer, inklusive funktionsmedlemsanrop, typomvandlingar och tilldelningar. Detta beskrivs ytterligare i §10.2.

En konverteringsoperatordeklaration som innehåller nyckelordet explicit introducerar en användardefinierad explicit konvertering. Explicita konverteringar kan ske i gjutna uttryck och beskrivs ytterligare i §10.3.

En konverteringsoperator konverterar från en källtyp som anges av parametertypen för konverteringsoperatorn till en måltyp som anges av konverteringsoperatorns returtyp.

För en viss källtyp S och måltyp T, om S eller T är nullbara värdetyper, låt S₀ och T₀ referera till deras underliggande typer; annars, är S₀ och T₀ lika med S respektive T. En klass eller struct tillåts deklarera en konvertering från en källtyp S till en måltyp T endast om allt av följande är sant:

  • S₀ och T₀ är olika typer.

  • Antingen S₀ eller T₀ är instanstypen för klassen eller structen som innehåller operatordeklarationen.

  • Varken S₀ eller 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 uttrycksdel exakt motsvarar blocket för en instansmetod med en void returtyp (§15.6.11).

Instanskonstruktörer ärver inte. Därför har en klass inga andra instanskonstruktorer än de som faktiskt deklareras i klassen, med undantag för att om en klass inte innehåller några instanskonstruktordeklarationer tillhandahålls automatiskt en standardinstanskonstruktor (§15.11.5).

Instanskonstruktorer anropas av object_creation_expressions (§12.8.17.2) och genom constructor_initializers.

15.11.2 Konstruktorinitierare

Alla instanskonstruktorer (förutom för klass object) inkluderar implicit en anrop av en annan instanskonstruktor omedelbart före constructor_body. Konstruktorn som implicit anropas bestäms av constructor_initializer:

  • En instanskonstruktorinitierare av formuläret base(argument_list) (där argument_list är valfritt) gör att en instanskonstruktor från den direkta basklassen anropas. Konstruktorn väljs med hjälp av argument_list och reglerna för överbelastningsmatchning i §12.6.4. Uppsättningen med kandidatinstanskonstruktorer består av alla tillgängliga instanskonstruktorer för den direkta basklassen. Om den här uppsättningen är tom, eller om en enda bästa instanskonstruktor inte kan identifieras, uppstår ett kompileringsfel.
  • En instanskonstruktorinitierare av formuläret this(argument_list) (där argument_list är valfritt) anropar en annan instanskonstruktor från samma klass. Konstruktorn väljs med hjälp av argument_list och reglerna för överbelastningsmatchning av §12.6.4. Uppsättningen med kandidatinstanskonstruktorer består av alla instanskonstruktorer som deklarerats i själva klassen. Om den resulterande uppsättningen av tillämpliga instanskonstruktorer är tom, eller om en enda bästa instanskonstruktor inte kan identifieras, uppstår ett kompileringsfel. Om en instanskonstruktordeklaration anropar sig själv via en kedja med en eller flera konstruktorinitierare uppstår ett kompileringsfel.

Om en instanskonstruktor inte har någon konstruktorinitierare tillhandahålls implicit en konstruktorinitierare av formuläret base() .

Obs! En instanskonstruktordeklaration av formuläret

C(...) {...}

är exakt likvärdigt med

C(...) : base() {...}

slutkommentar

Omfånget för de parametrar som anges av parameter_list för en instanskonstruktordeklaration innehåller konstruktorinitieraren för den deklarationen. Därför tillåts en konstruktorinitierare att komma åt konstruktorns parametrar.

Exempel:

class A
{
    public A(int x, int y) {}
}

class B: A
{
    public B(int x, int y) : base(x + y, x - y) {}
}

slutexempel

En instanskonstruktorinitierare kan inte komma åt den instans som skapas. Därför är det ett kompileringsfel att referera till detta i ett argumentuttryck för konstruktorns initialiserare, eftersom det är ett kompileringsfel för ett argumentuttryck för att referera till alla instansmedlemmar via en simple_name.

15.11.3 Instansvariabelinitierare

När en icke-extern instanskonstruktor inte har någon konstruktorinitierare, eller om den har en konstruktorinitierare av formuläret base(...), utför konstruktorn implicit de initieringar som anges av variable_initializeri instansfälten som deklareras i dess klass. Detta motsvarar en sekvens av tilldelningsoperationer som körs omedelbart vid inträdet i konstruktorn och före det implicita anropet av den direkta basklasskonstruktorn. Variabelinitierarna körs i textordningen där de visas i klassdeklarationen (§15.5.6).

Variabelinitierare behöver inte köras av externa instanskonstruktorer.

15.11.4 Konstruktörskörning

Variabelinitierare omvandlas till tilldelningsinstruktioner och dessa tilldelningsinstruktioner körs före anropet av basklassinstanskonstruktorn. Den här ordningen säkerställer att alla instansfält initieras av deras variabelinitierare innan några instruktioner som har åtkomst till den instansen körs.

Exempel: Givet följande:

class A
{
    public A()
    {
        PrintFields();
    }

    public virtual void PrintFields() {}
}
class B: A
{
    int x = 1;
    int y;

    public B()
    {
        y = -1;
    }

    public override void PrintFields() =>
        Console.WriteLine($"x = {x}, y = {y}");
}

när nytt B() används för att skapa en instans 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 Standardkonstruktörer

Om en klass inte innehåller några instanskonstruktordeklarationer tillhandahålls automatiskt en standardinstanskonstruktor. Den standardkonstruktorn anropar helt enkelt en konstruktor för den direkta basklassen, som om den hade en konstruktorinitierare av formuläret base(). Om klassen är abstrakt skyddas den deklarerade tillgängligheten för standardkonstruktorn. Annars är den deklarerade tillgängligheten för standardkonstruktorn offentlig.

Obs! Därför är standardkonstruktorn alltid i formen av

protected C(): base() {}

eller

public C(): base() {}

där C är namnet på klassen.

slutkommentar

Om överbelastningsupplösningen inte kan fastställa en unik bästa kandidat för basklasskonstruktorns initialiserare uppstår ett kompileringsfel.

Exempel: I följande kod

class Message
{
    object sender;
    string text;
}

en standardkonstruktor tillhandahålls eftersom klassen inte innehåller några instanskonstruktordeklarationer. Exemplet är alltså exakt detsamma som

class Message
{
    object sender;
    string text;

    public Message() : base() {}
}

slutexempel

15.12 Statiska konstruktorer

En statisk konstruktor är en medlem som implementerar de åtgärder som krävs för att initiera en stängd klass. Statiska konstruktorer deklareras med static_constructor_declaration s:

static_constructor_declaration
    : attributes? static_constructor_modifiers identifier '(' ')'
        static_constructor_body
    ;

static_constructor_modifiers
    : 'static'
    | 'static' 'extern' unsafe_modifier?
    | 'static' unsafe_modifier 'extern'?
    | 'extern' 'static' unsafe_modifier?
    | 'extern' unsafe_modifier 'static'
    | unsafe_modifier 'static' 'extern'?
    | unsafe_modifier 'extern' 'static'
    ;

static_constructor_body
    : block
    | '=>' expression ';'
    | ';'
    ;

unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).

En static_constructor_declaration kan innehålla en uppsättning attribut (§22) och en extern modifierare (§15.6.8).

Identifieraren för en static_constructor_declaration ska namnge den klass där den statiska konstruktorn deklareras. Om något annat namn anges uppstår ett kompileringsfel.

När en statisk konstruktordeklaration innehåller en extern modifierare sägs den statiska konstruktorn vara en extern statisk konstruktor. Eftersom en extern statisk konstruktordeklaration inte tillhandahåller någon faktisk implementering består dess static_constructor_body av ett semikolon. För alla andra statiska konstruktordeklarationer består static_constructor_body av antingen

  • ett block som anger vilka instruktioner som ska köras för att initiera klassen, eller
  • en uttryckstext, som består av => följt av ett uttryck och ett semikolon, och anger ett enda uttryck som ska köras för att initiera klassen.

En static_constructor_body som är ett block eller uttrycksblock motsvarar exakt metodkroppen hos en statisk metod med en void enligt §15.6.11.

Statiska konstruktorer ärvs inte och kan inte anropas direkt.

Den statiska konstruktorn för en sluten klass körs högst en gång i en viss programdomän. Körningen av en statisk konstruktor utlöses av den första av följande händelser som inträffar inom en programdomän:

  • En instans av klassen skapas.
  • Alla statiska medlemmar i klassen refereras till.

Om en klass innehåller metoden Main (§7.1) där körningen börjar körs den statiska konstruktorn för den Main klassen innan metoden anropas.

För att initiera en ny typ av sluten klass skapas först en ny uppsättning statiska fält (§15.5.2) för den specifika stängda typen. Vart och ett av de statiska fälten ska initieras till standardvärdet (§15.5.5). Så här gör du:

  • Om det antingen inte finns någon statisk konstruktor eller en icke-extern statisk konstruktor:
    • De statiska fältinitierarna (§15.5.6.2) ska utföras för dessa statiska fält.
    • Sedan ska den statiska konstruktorn, om någon, som inte är extern, utföras.
  • Annars ska den utföras om det finns en extern statisk konstruktor. Statiska variabelinitierare krävs inte för att köras av externa statiska konstruktorer.

Exempel: Exemplet

class Test
{
    static void Main()
    {
        A.F();
        B.F();
    }
}

class A
{
    static A()
    {
        Console.WriteLine("Init A");
    }

    public static void F()
    {
        Console.WriteLine("A.F");
    }
}

class B
{
    static B()
    {
        Console.WriteLine("Init B");
    }

    public static void F()
    {
        Console.WriteLine("B.F");
    }
}

måste generera utdata:

Init A
A.F
Init B
B.F

eftersom körningen av 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. Ys initierare gör att konstruktorn A för static körs eftersom värdet A.X refereras till. Den statiska konstruktorn A i sin tur fortsätter på att beräkna värdet av X, 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 Finaliserare

Obs! I en tidigare version av den här specifikationen kallades det som nu kallas "finalizer" för en "destructor". Erfarenheten har visat att termen "destructor" orsakade förvirring och ofta resulterade i felaktiga förväntningar, särskilt för programmerare som känner till C++. I C++ anropas en destruktor på ett bestämt sätt, medan en finalizer i C# inte gör det. För att få avgörande beteende från C#, bör man använda Dispose. slutkommentar

En finalizer är en medlem som implementerar de åtgärder som krävs för att slutföra en instans av en klass. En finaliserare deklareras med hjälp av en finalizer_declaration:

finalizer_declaration
    : attributes? '~' identifier '(' ')' finalizer_body
    | attributes? 'extern' unsafe_modifier? '~' identifier '(' ')'
      finalizer_body
    | attributes? unsafe_modifier 'extern'? '~' identifier '(' ')'
      finalizer_body
    ;

finalizer_body
    : block
    | '=>' expression ';'
    | ';'
    ;

unsafe_modifier (§23.2) är endast tillgänglig i osäker kod (§23).

En finalizer_declaration kan innehålla en uppsättning attribut (§22).

Identifieraren för en finalizer_declarator ska namnge den klass där slutdatorn deklareras. Om något annat namn anges uppstår ett kompileringsfel.

När en slutklareringsdeklaration innehåller en extern modifierare sägs finalatorn vara en extern finalator. Eftersom en extern slutklareringsdeklaration inte innehåller någon faktisk implementering består dess finalizer_body av ett semikolon. För alla andra finalizers består finalizer_body av antingen

  • ett block som anger vilka instruktioner som ska köras för att slutföra en instans av klassen.
  • eller en uttryckstext, som består av => följt av ett uttryck och ett semikolon, och anger ett enda uttryck som ska köras för att slutföra en instans av klassen.

En finalizer_body som är ett block eller uttrycksblock motsvarar exakt method_body av en instansmetod med en void returtyp (§15.6.11).

Finalizers ärvs inte. En klass har alltså inga finaliseringsmetoder förutom den som kan deklareras i den klassen.

Obs! Eftersom en finalator krävs för att inte ha några parametrar kan den inte överbelastas, så en klass kan ha högst en finalator. slutkommentar

Finalisatorer anropas automatiskt och kan inte anropas uttryckligen. En instans blir berättigad till slutförande när det inte längre är möjligt för någon kod att använda den instansen. Körning av finalizern för instansen kan ske när som helst efter att instansen har blivit berättigad till slutförande (§7.9). När en instans avslutas, anropas finalisatorerna i instansens arvkedja i ordning, från den mest härledda till den minst härledda. En finalizer kan köras på valfri tråd. Mer information om de regler som styr när och hur en finalator körs finns i §7.9.

Exempel: Utdata från exemplet

class A
{
    ~A()
    {
        Console.WriteLine("A's finalizer");
    }
}

class B : A
{
    ~B()
    {
        Console.WriteLine("B's finalizer");
    }
}

class Test
{
    static void Main()
    {
        B b = new B();
        b = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

är

B's finalizer
A's finalizer

sedan finalizers i en arvkedja anropas i ordning, från mest härledd till minst härledd.

slutexempel

Finalizers implementeras genom att ersätta den virtuella metoden 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.Objects Finalize metod.

slutexempel

För en diskussion om beteendet när ett undantag utlöses från en finalator, se §21.4.

15.14 Asynkrona Funktioner

15.14.1 Allmänt

En metod (§15.6) eller anonym funktion (§12.19) med async modifieraren kallas för en async function. I allmänhet används termen asynkron för att beskriva alla typer av funktioner som har async modifieraren.

Det är ett kompileringsfel om parameterlistan i en asynkron funktion specificerar någon in, out, eller ref-parameter, eller någon parameter av ref struct-typen.

Return_type för en asynkron metod ska vara antingen void, en aktivitetstyp eller en asynkron iteratortyp (§15.15). För en asynkron metod som genererar ett resultatvärde ska en aktivitetstyp eller en asynkron iteratortyp (§15.15.3) vara generisk. För en asynkron metod som inte ger ett resultatvärde ska en aktivitetstyp inte vara generisk. Sådana typer anges i den här specifikationen som «TaskType»<T> respektive «TaskType». Standardbibliotekstypen System.Threading.Tasks.Task och typerna som konstruerats från System.Threading.Tasks.Task<TResult> och System.Threading.Tasks.ValueTask<T> är aktivitetstyper, samt en typ av klass, struct eller gränssnitt som är associerad med en aktivitetsbyggaretyp via attributet System.Runtime.CompilerServices.AsyncMethodBuilderAttribute. Sådana typer anges i den här specifikationen som «TaskBuilderType»<T> och «TaskBuilderType». En aktivitetstyp kan ha högst en typparameter och kan inte kapslas i en allmän typ.

En asynkron metod som returnerar en task-typ sägs vara task-returning.

Aktivitetstyper kan variera i sin exakta definition, men från språkets synvinkel är en aktivitetstyp i något av tillstånden ofullständig, lyckades eller felade. En felaktig uppgift registrerar ett relevant undantag. Lyckad«TaskType»<T> registrerar ett resultat av typen T. Task-typer är väntbara, och uppgifter kan därför vara operander för väntuttryck (§12.9.8).

Exempel: Aktivitetstypen MyTask<T> är associerad med aktivitetsbyggarens 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.14.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 uppgiftsbyggartypen också vara kapslad i samma typ. slutkommentar

En asynkron funktion har förmåga att avbryta utvärdering genom await-uttryck (§12.9.8) i sin kropp. Utvärderingen kan senare återupptas vid det inväntningsuttryck där den avbröts, med hjälp av ett återtagandedeligat. Återtagandedelegaten är av typen System.Action, och när den anropas återupptas utvärderingen av den asynkrona funktionens anrop från det väntande uttrycket där det avbröts. Den aktuella uppringaren för ett asynkront funktionsanrop är den ursprungliga uppringaren om funktionsanropet aldrig har avbrutits eller annars den senaste uppringaren av återupptagningsdelegeringen.

15.14.2 Byggmönster av aktivitetstyp

En uppgiftsbyggartyp kan ha högst en typparameter och kan inte kapslas i en generisk typ. En typ av aktivitetsbyggare ska ha följande medlemmar (för icke-generiska aktivitetsbyggartyper har SetResult inga parametrar) med deklarerad public tillgänglighet:

class «TaskBuilderType»<T>
{
    public static «TaskBuilderType»<T> Create();
    public void Start<TStateMachine>(ref TStateMachine stateMachine)
                where TStateMachine : IAsyncStateMachine;
    public void SetStateMachine(IAsyncStateMachine stateMachine);
    public void SetException(Exception exception);
    public void SetResult(T result);
    public void AwaitOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : INotifyCompletion
        where TStateMachine : IAsyncStateMachine;
    public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
        ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : ICriticalNotifyCompletion
        where TStateMachine : IAsyncStateMachine;
    public «TaskType»<T> Task { get; }
}

En kompilator ska generera kod som använder "TaskBuilderType" för att implementera semantiken för att pausa och återuppta utvärderingen av asynkron funktion. En kompilator ska använda "TaskBuilderType" på följande sätt:

  • «TaskBuilderType».Create() anropas för att skapa en instans av «TaskBuilderType», med 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 anropa stateMachine.MoveNext() antingen i Start() eller efter att Start() har returnerat för att driva tillståndsmaskinen framåt.
  • Efter att Start() har returnerat, anropar async metoden builder.Task för att uppgiften ska returnera från async-metoden.
  • Varje anrop till stateMachine.MoveNext() kommer att avancera tillståndsmaskinen.
  • Om tillståndsmaskinen slutförs framgångsrikt anropas builder.SetResult(), med metodens returvärde, om det finns något.
  • Annars, om ett undantag kastas i tillståndsmaskinen, anropas ebuilder.SetException(e).
  • Om tillståndsdatorn når ett await expr uttryck expr.GetAwaiter() anropas.
  • Om väntaren implementerar ICriticalNotifyCompletion och IsCompleted är falskt, anropar tillståndsmaskinen builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine).
    • AwaitUnsafeOnCompleted() ska anropa awaiter.UnsafeOnCompleted(action) med en Action som anropar stateMachine.MoveNext() när väntaren är klar.
  • Annars anropar tillståndsmaskinen builder.AwaitOnCompleted(ref awaiter, ref stateMachine).
    • AwaitOnCompleted() ska anropa awaiter.OnCompleted(action) med en Action som anropar stateMachine.MoveNext() när väntaren är klar.
  • SetStateMachine(IAsyncStateMachine) kan anropas av den kompilatorgenererade IAsyncStateMachine implementeringen för att identifiera instansen av byggaren som är associerad med en tillståndsdatorinstans, särskilt när tillståndsdatorn implementeras som en värdetyp.
    • Om byggaren anropar stateMachine.SetStateMachine(stateMachine), kommer stateMachine anropa builder.SetStateMachine(stateMachine) på den byggarinstans som är associerad medstateMachine.

Notera: För både SetResult(T result) och «TaskType»<T> Task { get; } måste parametern och argumentet vara identitetskonverterbara till T. Detta gör att en uppgiftstypbyggare kan stödja typer som tuplar, där två typer som inte är desamma kan konverteras identiskt. slutkommentar

15.14.3 Utvärdering av en aktivitetsreturerande asynkron funktion

Anrop av en aktivitetsreturerande asynkron funktion gör att en instans av den returnerade aktivitetstypen genereras. Detta kallas för returuppgiften för den asynkrona funktionen. Aktiviteten är ursprungligen i ett ofullständigt tillstånd.

Den asynkrona funktionskroppen utvärderas sedan tills den antingen pausas (genom att nå await-uttrycket) eller avslutas, då kontrollen returneras till anroparen, tillsammans med returuppgiften.

När kroppen av async-funktionen avslutas, flyttas returuppgiften från det ofullständiga tillståndet.

  • Om funktionskroppen avslutas som resultat av att nå en return-sats eller slutet av kroppen, registreras eventuellt resultatvärde i returuppgiften, som sätts i ett succeeded-tillstånd.
  • Om funktionskroppen avslutas på grund av en oavlyssnad OperationCanceledException, registreras undantaget i återgångsuppgiften som sätts i läget avbruten.
  • Om funktionskroppen avslutas som ett resultat av någon annan oinfångad undantag (§13.10.6), registreras undantaget i returuppgiften som försätts i ett faulted tillstånd.

15.14.4 Utvärdering av en asynkron funktion som inte returnerar något värde

Om returtypen för funktionen async är 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 framgångsrikt eller gör att ett ohanterat undantag genereras.

På så sätt kan kontexten hålla reda på hur många void-returnerande asynkrona funktioner som körs under den och bestämma hur undantag ska hanteras utifrån dessa.

15.15 Synkrona och asynkrona iteratorer

15.15.1 Allmänt

En funktionsmedlem (§12.6) eller lokal funktion (§13.6.4) som implementeras med hjälp av ett iteratorblock (§13.3) kallas en iterator. Ett iteratorblock kan användas som en funktionsmedlems brödtext så länge som returtypen för motsvarande funktionsmedlem är ett av uppräkningsgränssnitten (§15.15.2) eller något av de uppräkningsbara gränssnitten (§15.15.3).

En asynkron funktion (§15.14) som implementeras med hjälp av ett iteratorblock (§13.3) kallas för en asynkron iterator. Ett asynkront iteratorblock kan användas som en funktionsmedlems brödtext så länge som returtypen för motsvarande funktionsmedlem är de asynkrona uppräkningsgränssnitten (§15.15.2) eller de asynkrona uppräkningsbara gränssnitten (§15.15.3).

Ett iteratorblock kan inträffa som en method_body, operator_body eller accessor_body, medan händelser, instanskonstruktorer, statiska konstruktorer och finalator inte ska implementeras som synkrona eller asynkrona iteratorer.

När en funktionsmedlem eller lokal funktion implementeras med hjälp av ett iteratorblock är det ett kompileringsfel för parameterlistan av funktionsmedlemmen att specificera några in, out, eller ref parametrar eller en parameter av en ref struct typ.

15.15.2 Uppräkningsgränssnitt

Uppräkningsgränssnitten är det icke-generiska gränssnittet System.Collections.IEnumerator och alla instansier av de generiska gränssnitten System.Collections.Generic.IEnumerator<T>.

De asynkrona uppräkningsgränssnitten är alla instansier av det generiska gränssnittet System.Collections.Generic.IAsyncEnumerator<T>.

För korthets skull refereras dessa gränssnitt i den här underklausulen och dess syskon som IEnumerator, IEnumerator<T>, och IAsyncEnumerator<T>.

15.15.3 Uppräkningsbara gränssnitt

De uppräkningsbara gränssnitten är det icke-generiska gränssnittet System.Collections.IEnumerable och alla instansieringar av de generiska gränssnitten System.Collections.Generic.IEnumerable<T>.

De asynkrona uppräkningsbara gränssnitten är alla instansier av det generiska gränssnittet System.Collections.Generic.IAsyncEnumerable<T>.

För korthets skull refereras dessa gränssnitt i den här underklausulen och dess syskon som IEnumerable, IEnumerable<T>, och IAsyncEnumerable<T>.

15.15.4 Avkastningstyp

En iterator genererar en sekvens med värden, alla av samma typ. Den här typen kallas för iteratorns avkastningstyp.

  • Avkastningstypen för en iterator som returnerar IEnumerator eller IEnumerable är object.
  • Avkastningstypen för en iterator som returnerar en IEnumerator<T>, IAsyncEnumerator<T>, IEnumerable<T>eller IAsyncEnumerable<T> är T.

15.15.5 Uppräkningsobjekt

15.15.5.1 Allmänt

När en funktionsmedlem eller lokal funktion som returnerar en uppräkningsgränssnittstyp implementeras med hjälp av ett iteratorblock, körs inte koden i iteratorblocket direkt när funktionen anropas. I stället skapas och returneras ett uppräkningsobjekt. Det här objektet kapslar in koden som anges i iteratorblocket och körningen av koden i iteratorblocket inträffar när uppräkningsobjektets MoveNext eller MoveNextAsync -metoden anropas. Ett uppräkningsobjekt har följande egenskaper:

  • Den implementerar System.IDisposable, IEnumerator och IEnumerator<T>, eller System.IAsyncDisposable och IAsyncEnumerator<T>, där T är iteratorns avkastningstyp.
  • Den initieras med en kopia av argumentvärdena (om några) och instansvärdet skickas till funktionsmedlemmen.
  • Det har fyra potentiella tillstånd, före, körs, pausas och efter, och är ursprungligen i tillståndet före .

Ett uppräkningsobjekt är vanligtvis en instans av en kompilatorgenererad uppräkningsklass som kapslar in koden i iteratorblocket och implementerar uppräkningsgränssnitten, men andra implementeringsmetoder är möjliga. Om en uppräkningsklass genereras av kompilatorn, kapslas den klassen, direkt eller indirekt, i klassen som innehåller funktionsmedlemmen, den har privat tillgänglighet och har ett namn som är reserverat för kompilatoranvändning (§6.4.3).

Ett uppräkningsobjekt kan implementera fler gränssnitt än de som anges ovan.

Följande underfunktioner beskriver medlemmens beteende som krävs för att föra uppräknaren framåt, hämta det aktuella värdet från uppräknaren och ta bort resurser som används av uppräknaren. Dessa definieras i följande medlemmar för synkrona respektive asynkrona uppräknare:

  • Så här avancerar du uppräknaren: MoveNext och MoveNextAsync.
  • Så här hämtar du det aktuella värdet: Current.
  • Så här gör du av med resurser: Dispose och DisposeAsync.

Uppräkningsobjekt stöder IEnumerator.Reset inte metoden. Om du anropar den här metoden kastas ett System.NotSupportedException.

Synkrona och asynkrona iteratorblock skiljer sig åt eftersom asynkrona iteratormedlemmar returnerar uppgiftstyper och kan vänta.

15.15.5.2 Föra uppräknaren framåt

Metoderna MoveNext och MoveNextAsync för ett uppräkningsobjekt kapslar in koden för ett iteratorblock. Genom att anropa metoden MoveNext eller MoveNextAsync körs koden i iteratorblocket, och Current-egenskapen hos uppräkningsobjektet ställs in efter behov.

MoveNext returnerar ett bool värde vars innebörd beskrivs nedan. MoveNextAsync returnerar en ValueTask<bool> (§15.14.3). Resultatvärdet för aktiviteten som returneras från MoveNextAsync har samma betydelse som resultatvärdet från MoveNext. I följande beskrivning gäller MoveNext de åtgärder som beskrivs för MoveNextAsync med följande skillnad: Om det anges att MoveNext returnerar true eller falseanger MoveNextAsync dess uppgift till slutfört tillstånd och anger aktivitetens resultatvärde till motsvarande true värde eller false värde.

Den exakta åtgärd som utförs av MoveNext eller MoveNextAsync beror på tillståndet för uppräkningsobjektet när det anropas:

  • Om uppräkningsobjektets tillstånd är tidigare anropar du MoveNext:
    • Ändrar tillståndet till körande.
    • Initierar parametrarna (inklusive this) för iteratorblocket till argumentvärdena och instansvärdet som sparades när uppräkningsobjektet initierades.
    • Kör iteratorblocket från början tills körningen avbryts (enligt beskrivningen nedan).
  • Om tillståndet för uppräkningsobjektet är körs är resultatet av att anropa MoveNext ospecificerat.
  • Om uppräkningsobjektets tillstånd är pausat anropar du MoveNext:
    • Ändrar tillståndet till körande.
    • Återställer värdena för alla lokala variabler och parametrar (inklusive this) till de värden som sparades när körningen av iteratorblocket senast pausades.

      Obs! Innehållet i objekt som refereras till av dessa variabler kan ha ändrats sedan föregående anrop till MoveNext. slutkommentar

    • Återupptar körningen av iteratorblocket direkt efter yield-return-uttrycket som avbröt körningen och fortsätter tills körningen avbryts (enligt beskrivningen nedan).
  • Om tillståndet för uppräkningsobjektet är efter returnerar anropet MoveNext false.

När MoveNext iteratorblocket körs kan körningen avbrytas på fyra sätt: Med en yield return instruktion, med en yield break -instruktion, genom att stöta på slutet av iteratorblocket och genom ett undantag som genereras och sprids ut ur iteratorblocket.

  • När en yield return instruktion påträffas (§9.4.4.20):
    • Uttrycket som anges i instruktionen utvärderas, konverteras implicit till yield-typen och tilldelas Current-egenskapen i uppräkningsobjektet.
    • Utförandet av iteratorns kropp pausas. Värdena för alla lokala variabler och parametrar (inklusive this) sparas, liksom platsen för den här yield return instruktionen. Om satsen yield return finns inom ett eller flera try block, körs de associerade slutligen-blocken inte just nu.
    • Uppräkningsobjektets tillstånd ändras till pausat.
    • Metoden MoveNext returnerar true till anroparen, vilket indikerar att iterationen framgångsrikt 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 returnerar MoveNext 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 returnerar MoveNext till anroparen, vilket anger att iterationen är klar.
  • När ett undantag utlöses och sprids ut ur iteratorblocket:
    • Lämpliga finally block i iteratorns kropp har körts av undantagsspridningen.
    • Uppräkningsobjektets tillstånd ändras till efter.
    • Undantagsspridningen fortsätter till anroparen för MoveNext metoden.

15.15.5.3 Hämta det aktuella värdet

En uppräkningsobjekts egenskap påverkas Current av yield return -instruktioner i iteratorblocket.

Obs! Egenskapen Current är en synkron egenskap för både synkrona och asynkrona iteratorobjekt. slutkommentar

När ett uppräkningsobjekt är i suspenderat tillstånd, är värdet Current det värde som angavs av det föregående anropet till MoveNext. När ett uppräkningsobjekt finns i tillstånden före, körs eller efter är resultatet av åtkomsten Current ospecificerat.

För en iterator med en annan avkastningstyp än object motsvarar resultatet av att komma åt Current genom uppräkningsobjektets IEnumerable-implementering att komma åt Current genom uppräkningsobjektets IEnumerator<T>-implementering och att konvertera resultatet till object.

15.15.5.4 Bortskaffa resurser

Metoden Dispose eller DisposeAsync används för att rensa iterationen genom att föra uppräkningsobjektet till eftertillståndet .

  • Om uppräkningsobjektets tillstånd är före ändras tillståndet till Dispose när tillståndet anropas.
  • Om tillståndet för uppräkningsobjektet är körs är resultatet av att anropa Dispose ospecificerat.
  • Om uppräkningsobjektets tillstånd är suspenderat , anropar du Dispose:
    • Ändrar tillståndet till körande.
    • Kör slutligen block som om den senast utförda yield return instruktionen var 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.15.6 Uppräkningsbara objekt

15.15.6.1 Allmänt

När en funktionsmedlem eller lokal funktion som returnerar en uppräkningsbar gränssnittstyp implementeras med hjälp av ett iteratorblock, kör inte anropande av funktionsmedlemmen koden direkt i iteratorblocket. I stället skapas och returneras ett uppräkningsbart objekt.

Det uppräkningsbara objektets eller GetEnumerator -metoden returnerar ett uppräkningsobjekt GetAsyncEnumerator som kapslar in koden som anges i iteratorblocket och körningen av koden i iteratorblocket inträffar när uppräkningsobjektets eller MoveNext -metoden anropasMoveNextAsync. Ett uppräkningsbart objekt har följande egenskaper:

  • Den implementerar IEnumerable och IEnumerable<T> eller IAsyncEnumerable<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 enumerator. 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.15.6.2 Metoden GetEnumerator eller GetAsyncEnumerator

Ett uppräkningsbart objekt tillhandahåller en implementering av metoderna från GetEnumerator och gränssnitten IEnumerable och IEnumerable<T>. De två GetEnumerator metoderna delar en gemensam implementering som hämtar och returnerar ett tillgängligt uppräkningsobjekt. Uppräkningsobjektet initieras med argumentvärdena och instansvärdet som sparades när det uppräkningsbara objektet initierades, men i övrigt fungerar uppräkningsobjektet enligt beskrivningen i §15.15.5.

Ett asynkront uppräkningsbart objekt tillhandahåller en implementering av GetAsyncEnumerator gränssnittets IAsyncEnumerable<T> metod. Den här metoden returnerar ett tillgängligt asynkront uppräkningsobjekt. Uppräkningsobjektet initieras med argumentvärdena och instansvärdet som sparades när det uppräkningsbara objektet initierades, men i övrigt fungerar uppräkningsobjektet enligt beskrivningen i §15.15.5.