Dela via


12 uttryck

12.1 Allmänt

Ett uttryck är en sekvens med operatorer och operander. Den här satsen definierar syntaxen, ordningen för utvärdering av operander och operatorer samt innebörden av uttryck.

12.2 Uttrycksklassificeringar

12.2.1 Allmänt

Resultatet av ett uttryck klassificeras som något av följande:

  • Ett värde. Varje värde har en associerad typ.
  • En variabel. Om inget annat anges skrivs en variabel uttryckligen och har en associerad typ, nämligen variabelns deklarerade typ. En implicit typvariabel har ingen associerad typ.
  • En nullliteral. Ett uttryck med den här klassificeringen kan implicit konverteras till en referenstyp eller nullbar värdetyp.
  • En anonym funktion. Ett uttryck med den här klassificeringen kan implicit konverteras till en kompatibel ombudstyp eller uttrycksträdstyp.
  • En tuppeln. Varje tuppeln har ett fast antal element, var och en med ett uttryck och ett valfritt tuppelns elementnamn.
  • En egenskapsåtkomst. Varje egenskapsåtkomst har en associerad typ, nämligen egenskapens typ. Dessutom kan en egenskapsåtkomst ha ett associerat instansuttryck. När en åtkomst till en instansegenskap anropas blir resultatet av utvärderingen av instansuttrycket den instans som representeras av this (§12.8.14).
  • En indexerares åtkomst. Varje indexerares åtkomst har en associerad typ, nämligen indexerarens elementtyp. Dessutom har en indexerare åtkomst ett associerat instansuttryck och en associerad argumentlista. När en åtkomst till en indexerare anropas blir resultatet av utvärderingen av instansuttrycket den instans som representeras av this (§12.8.14), och resultatet av utvärderingen av argumentlistan blir parameterlistan för anropet.
  • Ingenting. Detta inträffar när uttrycket är ett anrop av en metod med returtypen void. Ett uttryck som klassificeras som ingenting är endast giltigt i samband med en statement_expression (§13.7) eller som brödtexten i en lambda_expression (§12.19).

För uttryck som förekommer som underuttryck av större uttryck, med de antecknade begränsningarna, kan resultatet också klassificeras som något av följande:

  • Ett namnområde. Ett uttryck med denna klassificering kan bara visas som vänster sida av en member_access (§12.8.7). I andra sammanhang orsakar ett uttryck som klassificeras som ett namnområde ett kompileringsfel.
  • En typ. Ett uttryck med denna klassificering kan bara visas som vänster sida av en member_access (§12.8.7). I andra sammanhang orsakar ett uttryck som klassificeras som en typ ett kompileringsfel.
  • En metodgrupp, som är en uppsättning överlagrade metoder till följd av ett medlemsuppslag (§12.5). En metodgrupp kan ha ett associerat instansuttryck och en argumentlista av associerad typ. När en instansmetod anropas blir resultatet av utvärderingen av instansuttrycket den instans som representeras av this (§12.8.14). En metodgrupp tillåts i en invocation_expression (§12.8.10) eller en delegate_creation_expression (§12.8.17.6) och kan implicit konverteras till en kompatibel delegattyp (§10.8). I andra sammanhang orsakar ett uttryck som klassificeras som en metodgrupp ett kompileringsfel.
  • En händelseåtkomst. Varje händelseåtkomst har en associerad typ, nämligen typen av händelse. Dessutom kan en händelseåtkomst ha ett associerat instansuttryck. En händelseåtkomst kan visas som den vänstra operanden för operatorerna += och -= (§12.21.5). I andra sammanhang orsakar ett uttryck som klassificeras som en händelseåtkomst ett kompileringsfel. När en åtkomst till en instanshändelse anropas blir resultatet av utvärderingen av instansuttrycket den instans som representeras av this (§12.8.14).
  • Ett utkastsuttryck som kan användas är flera kontexter för att utlösa ett undantag i ett uttryck. Ett utkastsuttryck kan konverteras av en implicit konvertering till valfri typ.

En egenskapsåtkomst eller indexerares åtkomst omklassificeras alltid som ett värde genom att utföra ett anrop av get-accessorn eller den angivna åtkomsten. Den specifika åtkomsten bestäms av kontexten för egenskapen eller indexerarens åtkomst: Om åtkomsten är målet för en tilldelning anropas den angivna accessorn för att tilldela ett nytt värde (§12.21.2). Annars anropas get-accessorn för att hämta det aktuella värdet (§12.2.2).

En instansåtkomst är en egenskapsåtkomst på en instans, en händelseåtkomst på en instans eller en indexerares åtkomst.

12.2.2 Uttrycksvärden

De flesta konstruktioner som omfattar ett uttryck kräver i slutändan att uttrycket anger ett värde. I sådana fall uppstår ett kompileringsfel om det faktiska uttrycket anger ett namnområde, en typ, en metodgrupp eller ingenting. Men om uttrycket anger en egenskapsåtkomst, en indexerareåtkomst eller en variabel ersätts värdet för egenskapen, indexeraren eller variabeln implicit:

  • Värdet för en variabel är helt enkelt det värde som för närvarande lagras på lagringsplatsen som identifieras av variabeln. En variabel ska anses definitivt tilldelad (§9.4) innan dess värde kan erhållas, eller på annat sätt uppstår ett kompileringsfel.
  • Värdet för ett egenskapsåtkomstuttryck hämtas genom att anropa egenskapens get-accessor. Om egenskapen inte har någon get-accessor uppstår ett kompileringsfel. Annars utförs ett funktionsmedlemsanrop (§12.6.6) och resultatet av anropet blir värdet för egenskapsåtkomstuttrycket.
  • Värdet för ett indexerares åtkomstuttryck hämtas genom att anropa indexerarens get-accessor. Om indexeraren inte har någon get-accessor uppstår ett kompileringsfel. Annars utförs ett funktionsmedlemsanrop (§12.6.6) med argumentlistan som är associerad med indexerarens åtkomstuttryck, och resultatet av anropet blir värdet för indexerarens åtkomstuttryck.
  • Värdet för ett tupppeluttryck erhålls genom att tillämpa en implicit tuppelns konvertering (§10.2.13) på typen av tuppelns uttryck. Det är ett fel att hämta värdet för ett tupppeluttryck som inte har någon typ.

12.3 Statisk och dynamisk bindning

12.3.1 Allmänt

Bindning är processen för att avgöra vad en åtgärd refererar till, baserat på typ eller värde för uttryck (argument, operander, mottagare). Till exempel bestäms bindningen av ett metodanrop baserat på typen av mottagare och argument. Bindningen av en operator bestäms baserat på typen av dess operander.

I C# bestäms bindningen av en åtgärd vanligtvis vid kompileringstid, baserat på kompileringstidstypen för dess underuttryck. På samma sätt identifieras och rapporteras felet av kompilatorn om ett uttryck innehåller ett fel. Den här metoden kallas statisk bindning.

Men om ett uttryck är ett dynamiskt uttryck (dvs. har typen dynamic) anger detta att alla bindningar som det deltar i ska baseras på dess körningstyp i stället för den typ som det har vid kompileringstid. Bindningen av en sådan åtgärd skjuts därför upp till den tidpunkt då åtgärden ska köras under körningen av programmet. Detta kallas dynamisk bindning.

När en åtgärd är dynamiskt bunden utförs liten eller ingen kontroll av kompilatorn. Om körningsbindningen i stället misslyckas rapporteras fel som undantag vid körning.

Följande åtgärder i C# omfattas av bindning:

  • Medlemsåtkomst: e.M
  • Metodanrop: e.M(e₁,...,eᵥ)
  • Delegera anrop: e(e₁,...,eᵥ)
  • Elementåtkomst: e[e₁,...,eᵥ]
  • Skapa objekt: ny C(e₁,...,eᵥ)
  • Överbelastade unary-operatorer: +, -, ! (endast logisk negation), ~, , ++--, , truefalse
  • Överlagrade binära operatorer: +, -, *, /, %, &, &&, |, ||, ??, ^, <<, >>, ==, !=, >, <, >=<=
  • Tilldelningsoperatorer: =, = ref, +=, -=, *=, /=, %=&=, |=, ^=, , <<=>>=
  • Implicita och explicita konverteringar

När inga dynamiska uttryck ingår är C# som standard statisk bindning, vilket innebär att kompileringstidstyperna för underuttryck används i urvalsprocessen. Men när en av underuttrycken i de åtgärder som anges ovan är ett dynamiskt uttryck, är åtgärden i stället dynamiskt bunden.

Det är ett kompileringsfel om ett metodanrop är dynamiskt bundet och någon av parametrarna, inklusive mottagaren, är indataparametrar.

12.3.2 Bindningstid

Statisk bindning sker vid kompileringstid, medan dynamisk bindning sker vid körning. I följande underinställningar refererar termen bindningstid till antingen kompileringstid eller körning, beroende på när bindningen äger rum.

Exempel: Följande illustrerar begreppen statisk och dynamisk bindning och bindningstid:

object o = 5;
dynamic d = 5;
Console.WriteLine(5); // static binding to Console.WriteLine(int)
Console.WriteLine(o); // static binding to Console.WriteLine(object)
Console.WriteLine(d); // dynamic binding to Console.WriteLine(int)

De första två anropen är statiskt bundna: överlagringen av Console.WriteLine plockas baserat på kompileringstidstypen för deras argument. Bindningstiden är därför kompileringstid.

Det tredje anropet är dynamiskt bundet: överlagringen av Console.WriteLine väljs baserat på körningstypen för argumentet. Detta beror på att argumentet är ett dynamiskt uttryck – dess kompileringstidstyp är dynamisk. Bindningstiden för det tredje anropet är därför körning.

slutexempel

12.3.3 Dynamisk bindning

Den här underklienten är informativ.

Med dynamisk bindning kan C#-program interagera med dynamiska objekt, dvs. objekt som inte följer de normala reglerna i C#-typsystemet. Dynamiska objekt kan vara objekt från andra programmeringsspråk med olika typer av system, eller objekt som är programmatiskt konfigurerade för att implementera sin egen bindningssemantik för olika åtgärder.

Den mekanism med vilken ett dynamiskt objekt implementerar sin egen semantik är implementeringsdefinierad. Ett givet gränssnitt – återigen implementeringsdefinierad – implementeras av dynamiska objekt för att signalera till C#-körningen att de har särskilda semantik. Så när åtgärder på ett dynamiskt objekt är dynamiskt bundna, tar deras egna bindningssemantik, snarare än C# som anges i den här specifikationen, över.

Syftet med dynamisk bindning är att tillåta interoperation med dynamiska objekt, men C# tillåter dynamisk bindning för alla objekt, oavsett om de är dynamiska eller inte. Detta möjliggör en smidigare integrering av dynamiska objekt, eftersom resultatet av åtgärder på dem kanske inte själva är dynamiska objekt, men fortfarande är av en typ som är okänd för programmeraren vid kompileringstiden. Dynamisk bindning kan också hjälpa till att eliminera felbenägen reflektionsbaserad kod även om inga objekt är inblandade är dynamiska objekt.

12.3.4 Typer av underuttryck

När en åtgärd är statiskt bunden anses typen av underuttryck (t.ex. en mottagare och ett argument, ett index eller en operand) alltid vara kompileringstidstypen för uttrycket.

När en åtgärd är dynamiskt bunden bestäms typen av underuttryck på olika sätt beroende på underuttryckens kompileringstidstyp:

  • En underuttryck av kompileringstypsdynamik anses ha den typ av faktiskt värde som uttrycket utvärderas till vid körning
  • En underuttryck vars kompileringstidstyp är en typparameter anses ha den typ som typparametern är bunden till vid körning
  • Annars anses underuttrycket ha sin kompileringstidstyp.

12.4 Operatorer

12.4.1 Allmänt

Uttryck konstrueras från operander och operatorer. Operatorerna för ett uttryck anger vilka åtgärder som ska tillämpas på operanderna.

Exempel: Exempel på operatorer är +, -, *, /och new. Exempel på operander är literaler, fält, lokala variabler och uttryck. slutexempel

Det finns tre typer av operatorer:

  • Unary-operatorer. De unary operatorerna tar en operand och använder antingen prefix notation (till exempel –x) eller postfix notation (till exempel x++).
  • Binära operatorer. De binära operatorerna tar två operander och alla använder infix-notation (till exempel x + y).
  • Ternary-operator. Endast en ternary-operator, ?:, finns. Den tar tre operander och använder infix-notation (c ? x : y).

Ordningen för utvärdering av operatorer i ett uttryck bestäms av operatorernas prioritet och associativitet (§12.4.2).

Operander i ett uttryck utvärderas från vänster till höger.

Exempel: I F(i) + G(i++) * H(i)anropas metoden F med det gamla värdet i, och sedan anropas metoden G med det gamla värdet i, och slutligen anropas metoden H med det nya värdet i. Detta är separat från och inte relaterat till operatorprioret. slutexempel

Vissa operatorer kan överbelastas. Operatoröverlagring (§12.4.3) tillåter att användardefinierade operatorimplementeringar anges för åtgärder där en eller båda operanderna är av en användardefinierad klass eller structtyp.

12.4.2 Operatorprioritet och associativitet

När ett uttryck innehåller flera operatorer styr operatorernas prioritet i vilken ordning de enskilda operatorerna utvärderas.

Obs! Uttrycket x + y * z utvärderas till exempel som x + (y * z) eftersom operatorn * har högre prioritet än den binära + operatorn. slutkommentar

Prioriteten för en operator fastställs genom definitionen av dess associerade grammatikproduktion.

Obs! Till exempel består en additive_expression av en sekvens med multiplicative_expressions avgränsade med + eller - operatorer, vilket ger + operatorerna och - lägre prioritet än operatorerna *, /och % . slutkommentar

Obs! Följande tabell sammanfattar alla operatorer i prioritetsordning från högsta till lägsta:

Underklient Kategori Operatorer
§12.8 Primär x.y x?.y f(x) a[x] a?[x] x++ x-- x! new typeof default checked unchecked delegate stackalloc
§12.9 Unär + - !x ~ ++x --x (T)x await x
§12.10 Multiplicative * / %
§12.10 Tillsats + -
§12.11 Shift << >>
§12.12 Relations- och typtestning < > <= >= is as
§12.12 Tillämplig == !=
§12.13 Logiskt OCH &
§12.13 Logisk XOR ^
§12.13 Logiskt ELLER \|
§12.14 Villkorsstyrd OCH &&
§12.14 Villkorsstyrd ELLER \|\|
§12.15 och §12.16 Null-sammanslagning och utkastsuttryck ?? throw x
§12.18 Villkorsstyrd ?:
§12.21 och §12.19 Tilldelnings- och lambda-uttryck = = ref *= /= %= += -= <<= >>= &= ^= \|= =>

slutkommentar

När en operande inträffar mellan två operatorer med samma prioritet styr operatorernas associativitet ordningen i vilken åtgärderna utförs:

  • Förutom tilldelningsoperatorerna och operatorn null coalescing är alla binära operatorer vänster-associativa, vilket innebär att åtgärder utförs från vänster till höger.

    Exempel: x + y + z utvärderas som (x + y) + z. slutexempel

  • Tilldelningsoperatorerna, null coalescing-operatorn och den villkorsstyrda operatorn (?:) är höger-associativa, vilket innebär att åtgärder utförs från höger till vänster.

    Exempel: x = y = z utvärderas som x = (y = z). slutexempel

Prioritet och associativitet kan styras med parenteser.

Exempel: x + y * z multiplicerar z y först med och lägger sedan till resultatet i x, men (x + y) * z lägger först till x och y multiplicerar sedan resultatet zmed . slutexempel

12.4.3 Överlagring av operatör

Alla unary och binära operatorer har fördefinierade implementeringar. Dessutom kan användardefinierade implementeringar införas genom att inkludera operatordeklarationer (§15.10) i klasser och structs. Användardefinierade operatorimplementeringar har alltid företräde framför fördefinierade operatorimplementeringar: Endast när det inte finns några tillämpliga användardefinierade operatorimplementeringar beaktas de fördefinierade operatorimplementeringarna enligt beskrivningen i §12.4.4 och §12.4.5.

De överlagbara unary-operatorerna är:

+ - ! (endast logisk negation) ~ ++ -- true false

Obs! Även om true och false inte används uttryckligen i uttryck (och därför inte ingår i prioritetstabellen i §12.4.2), betraktas de som operatorer eftersom de anropas i flera uttryckskontexter: Booleska uttryck (§12.24) och uttryck som involverar villkorsstyrda (§12.18) och villkorsstyrda logiska operatorer (§12.14). slutkommentar

Obs! Operatorn null-forgiving (postfix !, §12.8.9) är inte en överlagbar operator. slutkommentar

De överlagbara binära operatorerna är:

+  -  *  /  %  &  |  ^  <<  >>  ==  !=  >  <  <=  >=

Endast de operatorer som anges ovan kan överbelastas. I synnerhet är det inte möjligt att överbelasta medlemsåtkomst, metodanrop eller operatorerna =, , &&, ||, ??, uncheckedcheckednewtypeof=>?:, , defaultasoch .is

När en binär operator är överbelastad överbelastas även motsvarande sammansatta tilldelningsoperator, om någon, implicit.

Exempel: En överlagring av operatorn * är också en överlagring av operatorn *=. Detta beskrivs ytterligare i §12.21. slutexempel

Tilldelningsoperatorn (=) kan inte överbelastas. En tilldelning utför alltid ett enkelt lager av ett värde i en variabel (§12.21.2).

Cast-åtgärder, till exempel (T)x, överbelastas genom att tillhandahålla användardefinierade konverteringar (§10.5).

Obs! Användardefinierade konverteringar påverkar inte operatorernas is as beteende. slutkommentar

Elementåtkomst, till exempel a[x], anses inte vara en överlagringsbar operator. I stället stöds användardefinierad indexering via indexerare (§15.9).

I uttryck refereras operatorer med operator notation, och i deklarationer refereras operatorer med hjälp av funktionell notation. I följande tabell visas relationen mellan operator och funktionella noteringar för unary- och binära operatorer. I den första posten anger «op» alla överlagbara unary prefixoperatorer. I den andra posten anger «op» det unary postfixet ++ och -- operatorerna. I den tredje posten anger «op» alla överlagrbara binära operatorer.

Obs! Ett exempel på överlagring av operatorerna ++ och -- se §15.10.2. slutkommentar

Operator notation Funktionell notation
«op» x operator «op»(x)
x «op» operator «op»(x)
x «op» y operator «op»(x, y)

Användardefinierade operatordeklarationer kräver alltid att minst en av parametrarna är av den klass- eller structtyp som innehåller operatordeklarationen.

Obs! Därför är det inte möjligt för en användardefinierad operator att ha samma signatur som en fördefinierad operator. slutkommentar

Användardefinierade operatordeklarationer kan inte ändra syntax, prioritet eller associativitet för en operator.

Exempel: Operatorn / är alltid en binär operator, har alltid den prioritetsnivå som anges i §12.4.2 och är alltid vänstersociativ. slutexempel

Obs! Även om det är möjligt för en användardefinierad operator att utföra alla beräkningar som den vill, rekommenderas starkt implementeringar som ger andra resultat än de som intuitivt förväntas. En implementering av operatorn == bör till exempel jämföra de två operanderna för likhet och returnera ett lämpligt bool resultat. slutkommentar

Beskrivningarna av enskilda operatörer i §12.9 till och med §12.21 anger de fördefinierade implementeringarna av operatörerna och eventuella ytterligare regler som gäller för varje operatör. Beskrivningarna använder termerna unary operator overload resolution, binary operator overload resolution, numeric promotion och lifted operator definitions of which are found in the following subclauses.

12.4.4 Unary operatoröverbelastningsupplösning

En åtgärd i formuläret «op» x eller x «op», där «op» är en överlaglig unary-operator och x är ett uttryck av typen X, bearbetas på följande sätt:

  • Den uppsättning av kandidatanvändare som tillhandahålls av X åtgärden operator «op»(x) bestäms med hjälp av reglerna i §12.4.6.
  • Om uppsättningen med användardefinierade kandidatoperatorer inte är tom blir detta uppsättningen kandidatoperatorer för åtgärden. Annars blir de fördefinierade binära operator «op» implementeringarna, inklusive deras hävda formulär, uppsättningen kandidatoperatorer för åtgärden. De fördefinierade implementeringarna av en viss operator anges i beskrivningen av operatorn. De fördefinierade operatorerna som tillhandahålls av en uppräknings- eller delegattyp ingår endast i den här uppsättningen när bindningstidstypen – eller den underliggande typen om den är en nullbar typ – av antingen operand är uppräknings- eller delegattypen.
  • Reglerna för överbelastningsmatchning i §12.6.4 tillämpas på uppsättningen kandidatoperatorer för att välja den bästa operatören med avseende på argumentlistan (x), och denna operatör blir resultatet av överlagringslösningsprocessen. Om det inte går att välja en enda bästa operator uppstår ett bindningstidsfel.

12.4.5 Binär operatoröverbelastningsupplösning

En åtgärd i formuläret x «op» y, där «op» är en överlagbar binär operator, x är ett uttryck av typen X, och y är ett uttryck av typen Y, bearbetas på följande sätt:

  • Uppsättningen med användardefinierade kandidatoperatorer som tillhandahålls av X och Y för åtgärden operator «op»(x, y) bestäms. Uppsättningen består av förbundet mellan de kandidatoperatörer som tillhandahålls av X och de kandidatoperatörer som tillhandahålls av Y, var och en bestäms med hjälp av reglerna i §12.4.6. För den kombinerade uppsättningen sammanfogas kandidaterna på följande sätt:
    • Om X och Y är identitetsvertibla, eller om X och Y härleds från en gemensam bastyp, förekommer endast delade kandidatoperatorer i den kombinerade uppsättningen en gång.
    • Om det finns en identitetskonvertering mellan X och Yhar en operator «op»Y som tillhandahålls av Y samma returtyp som en «op»X som tillhandahålls av X och operandtyperna «op»Y av har en identitetskonvertering till motsvarande operandtyper «op»X , sker endast «op»X i uppsättningen.
  • Om uppsättningen med användardefinierade kandidatoperatorer inte är tom blir detta uppsättningen kandidatoperatorer för åtgärden. Annars blir de fördefinierade binära operator «op» implementeringarna, inklusive deras hävda formulär, uppsättningen kandidatoperatorer för åtgärden. De fördefinierade implementeringarna av en viss operator anges i beskrivningen av operatorn. För fördefinierade uppräknings- och ombudsoperatorer är de enda operatorer som anses vara de som tillhandahålls av en uppräknings- eller delegattyp som är bindningstidstypen för en av operanderna.
  • Reglerna för överbelastningsmatchning i §12.6.4 tillämpas på uppsättningen kandidatoperatorer för att välja den bästa operatören med avseende på argumentlistan (x, y), och denna operatör blir resultatet av överlagringslösningsprocessen. Om det inte går att välja en enda bästa operator uppstår ett bindningstidsfel.

12.4.6 Användardefinierade operatorer för kandidat

Med tanke på en typ T och en åtgärd operator «op»(A), där «op» är en överlagbar operator och A är en argumentlista, bestäms uppsättningen med kandidatanvändardefinierade operatorer som tillhandahålls av T operatorn «op»(A) enligt följande:

  • Fastställ typen T₀. Om T är en nullbar värdetyp T₀ är dess underliggande typ. Annars T₀ är den lika med T.
  • För alla operator «op» deklarationer i T₀ och alla upplyfta former av sådana aktörer, om minst en operatör är tillämplig (§12.6.4.2) med avseende på argumentlistan A, består uppsättningen av kandidatoperatorer av alla sådana tillämpliga operatörer i T₀.
  • Annars, om T₀ är object, är uppsättningen med kandidatoperatorer tom.
  • Annars är uppsättningen kandidatoperatorer som tillhandahålls av T₀ uppsättningen kandidatoperatorer som tillhandahålls av den direkta basklassen T₀för , eller den effektiva basklassen T₀ för om T₀ är en typparameter.

12.4.7 Numeriska kampanjer

12.4.7.1 Allmänt

Den här underklienten är informativ.

§12.4.7 och dess delklienter är en sammanfattning av den kombinerade effekten av:

  • reglerna för implicita numeriska konverteringar (§10.2.3);
  • reglerna för bättre konvertering (§12.6.4.7); och
  • de tillgängliga aritmetiska (§12.10), relationsoperatorerna (§12.12) och integralen logiska (§12.13.2).

Numerisk befordran består av att automatiskt utföra vissa implicita konverteringar av operanderna för de fördefinierade odefinierade och binära numeriska operatorerna. Numerisk befordran är inte en distinkt mekanism, utan snarare en effekt av att tillämpa överbelastningsmatchning på de fördefinierade operatorerna. Numerisk befordran påverkar inte specifikt utvärdering av användardefinierade operatorer, även om användardefinierade operatorer kan implementeras för att uppvisa liknande effekter.

Som ett exempel på numerisk befordran bör du överväga de fördefinierade implementeringarna av den binära * operatorn:

int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);

När regler för överbelastningsmatchning (§12.6.4) tillämpas på den här uppsättningen operatorer är effekten att välja den första av de operatorer för vilka implicita konverteringar finns från operandtyperna.

Exempel: För åtgärden b * s, där b är en byte och s är en short, väljer överlagringsupplösningen operator *(int, int) som den bästa operatorn. Effekten är därför att b och s konverteras till int, och typen av resultat är int. På samma sätt, för åtgärden i * d, där i är en int och d är en double, overload väljer operator *(double, double) upplösningen som den bästa operatorn. slutexempel

Slut på informativ text.

12.4.7.2 Icke-numeriska kampanjer

Den här underklienten är informativ.

Unary numeriska befordran sker för operander för fördefinierade +, -, och ~ unary operatorer. Unary numerisk befordran består helt enkelt av att konvertera operander av typen sbyte, , shortbyte, ushort, eller char för att skriva int. För den unary – operatorn konverterar unary numeriska kampanjer operander av typen uint till typ long.

Slut på informativ text.

12.4.7.3 Binära numeriska kampanjer

Den här underklienten är informativ.

Binär numerisk befordran sker för operanderna för de fördefinierade +operatorerna , , -*, /, %, , &, |, ^==>!=, , <, >=och <= binära operatorer. Binär numerisk befordran konverterar implicit båda operanderna till en gemensam typ som, om det gäller icke-relationsoperatorer, också blir resultattypen för åtgärden. Binär numerisk befordran består av att tillämpa följande regler i den ordning de visas här:

  • Om någon av operanderna är av typen decimalkonverteras den andra operanden till typen decimal, eller så uppstår ett bindningsfel om den andra operanden är av typen float eller double.
  • Annars konverteras den andra operanden om doublenågon av operanderna är av typen double.
  • Annars konverteras den andra operanden om floatnågon av operanderna är av typen float.
  • Annars, om någon av operanderna är av typen ulong, konverteras den andra operanden till typen ulong, eller så uppstår ett bindningstidsfel om den andra operanden är av type sbyte, short, inteller long.
  • Annars konverteras den andra operanden om longnågon av operanderna är av typen long.
  • Annars konverteras båda operanderna till longtypen om någon av operanderna är av typen sbyteuint , shorteller int.
  • Annars konverteras den andra operanden om uintnågon av operanderna är av typen uint.
  • Annars konverteras båda operanderna till att skriva int.

Obs! Den första regeln tillåter inte åtgärder som blandar decimal typen med typerna double och float . Regeln följer av det faktum att det inte finns några implicita konverteringar mellan decimal typen och typerna double och float . slutkommentar

Obs! Observera också att det inte är möjligt för en operand att vara av typen ulong när den andra operanden är av en signerad integraltyp. Anledningen är att det inte finns någon integrerad typ som kan representera hela intervallet av ulong såväl som de signerade integraltyperna. slutkommentar

I båda fallen ovan kan ett cast-uttryck användas för att explicit konvertera en operande till en typ som är kompatibel med den andra operanden.

Exempel: I följande kod

decimal AddPercent(decimal x, double percent) =>
    x * (1.0 + percent / 100.0);

ett bindningstidsfel uppstår eftersom en decimal inte kan multipliceras med en double. Felet löses genom att den andra operanden uttryckligen konverteras till decimal, enligt följande:

decimal AddPercent(decimal x, double percent) =>
    x * (decimal)(1.0 + percent / 100.0);

slutexempel

Slut på informativ text.

12.4.8 Upplyfta operatorer

Med hävda operatorer kan fördefinierade och användardefinierade operatorer som använder icke-nullbara värdetyper även användas med nullbara former av dessa typer. Hissade operatorer är konstruerade från fördefinierade och användardefinierade operatorer som uppfyller vissa krav, enligt beskrivningen i följande:

  • För de unary operatorerna +, ++, -, --, !(logisk negation) och ~, finns det en hävd form av en operator om både operand- och resultattyperna är icke-nullbara värdetyper. Det upplyfta formuläret skapas genom att en enda ? modifierare läggs till i operand- och resultattyperna. Den lyfte operatorn genererar ett null värde om operand är null. Annars packar den lyftade operatorn upp operanden, tillämpar den underliggande operatorn och omsluter resultatet.
  • För de binära operatorerna +, , *-, /, %, &, ^|, , <<och >>, finns en hävd form av en operator om operand- och resultattyperna alla typer av icke-nullbara värden. Det upplyfta formuläret skapas genom att en enda ? modifierare läggs till i varje operand och resultattyp. Den lyftade operatorn genererar ett null värde om en eller båda operanderna är (ett undantag är & null typens operatorer och | , enligt beskrivningen bool? i §12.13.5). Annars packar den lyftade operatorn upp operanderna, tillämpar den underliggande operatorn och omsluter resultatet.
  • För likhetsoperatorerna == och !=finns en hävd form av en operator om operandtyperna både är icke-nullbara värdetyper och om resultattypen är bool. Det upplyfta formuläret skapas genom att en enda ? modifierare läggs till i varje operandtyp. Den lyftande operatorn anser att två null värden är lika med och ett null värde som är ojämlikt för alla icke-värdennull . Om båda operanderna inte är -null, skriver den lyftade operatorn upp operanderna och tillämpar den underliggande operatorn för att producera resultatet bool .
  • För relationsoperatorerna <finns , >, <=och >=, en upplyft form av en operator om operandtyperna både är icke-nullbara värdetyper och om resultattypen är bool. Det upplyfta formuläret skapas genom att en enda ? modifierare läggs till i varje operandtyp. Den lyfte operatorn genererar värdet false om en eller båda operanderna är null. I annat fall skriver den lyftade operatorn upp operanderna och tillämpar den underliggande operatorn för att generera resultatet bool .

12.5 Medlemssökning

12.5.1 Allmänt

En medlemssökning är den process där innebörden av ett namn i kontexten för en typ bestäms. En medlemssökning kan ske som en del av utvärderingen av en simple_name (§12.8.4) eller en member_access (§12.8.7) i ett uttryck. Om simple_name eller member_access inträffar som primary_expression av en invocation_expression (§12.8.10.2), sägs medlemmen anropas.

Om en medlem är en metod eller händelse, eller om det är en konstant, fält eller egenskap av antingen en delegattyp (§20) eller typen dynamic (§8.2.4), sägs medlemmen vara anropsbar.

Medlemssökningen tar inte bara hänsyn till namnet på en medlem utan även antalet typparametrar som medlemmen har och om medlemmen är tillgänglig. För medlemssökning har generiska metoder och kapslade generiska typer antalet typparametrar som anges i respektive deklaration och alla andra medlemmar har nolltypsparametrar.

En medlemssökning av ett namn N med K typargument i en typ T bearbetas på följande sätt:

  • Först bestäms en uppsättning tillgängliga medlemmar med namnet N :
    • Om T är en typparameter är uppsättningen en union av uppsättningarna med tillgängliga medlemmar som namnges N i var och en av de typer som anges som en primär begränsning eller sekundär begränsning (§15.2.5) för T, tillsammans med uppsättningen med tillgängliga medlemmar med namnet N i object.
    • I annat fall består uppsättningen av alla tillgängliga (§7.5) medlemmar med namnet N i T, inklusive ärvda medlemmar och de tillgängliga medlemmar som nämns N i object. Om T är en konstruerad typ erhålls uppsättningen medlemmar genom att ersätta typargument enligt beskrivningen i §15.3.3. Medlemmar som innehåller en override modifierare undantas från uppsättningen.
  • K Om är noll tas sedan alla kapslade typer vars deklarationer innehåller typparametrar bort. Om K inte är noll tas alla medlemmar med ett annat antal typparametrar bort. När K är noll tas metoder med typparametrar inte bort, eftersom typinferensprocessen (§12.6.3) kan härleda typargumenten.
  • Om medlemmen anropas tas sedan alla icke-anropbara medlemmar bort från uppsättningen.
  • Därefter tas medlemmar som är dolda av andra medlemmar bort från uppsättningen. För varje medlem S.M i uppsättningen, där S är den typ där medlemmen M deklareras, tillämpas följande regler:
    • Om M är en konstant, fält, egenskap, händelse eller uppräkningsmedlem tas alla medlemmar som deklarerats i en bastyp av S bort från uppsättningen.
    • Om M är en typdeklaration tas alla icke-typer som deklarerats i en bastyp av S bort från uppsättningen, och alla typdeklarationer med samma antal typparametrar som M deklareras i en bastyp tas S bort från uppsättningen.
    • Om M är en metod tas alla icke-metodmedlemmar som deklarerats i en bastyp bort S från uppsättningen.
  • Därefter tas gränssnittsmedlemmar som är dolda av klassmedlemmar bort från uppsättningen. Det här steget har bara en effekt om T är en typparameter och T har både en annan effektiv basklass än object och en icke-tom effektiv gränssnittsuppsättning (§15.2.5). För varje medlem S.M i uppsättningen, där S är den typ där medlemmen M deklareras, tillämpas följande regler om S är en annan klassdeklaration än object:
    • Om M är en konstant, fält, egenskap, händelse, uppräkningsmedlem eller typdeklaration, tas alla medlemmar som deklarerats i en gränssnittsdeklaration bort från uppsättningen.
    • Om M är en metod tas alla icke-metodmedlemmar som deklarerats i en gränssnittsdeklaration bort från uppsättningen, och alla metoder med samma signatur som M deklareras i en gränssnittsdeklaration tas bort från uppsättningen.
  • Efter att ha tagit bort dolda medlemmar bestäms slutligen resultatet av sökningen:
    • Om uppsättningen består av en enskild medlem som inte är en metod är den här medlemmen resultatet av sökningen.
    • Annars, om uppsättningen endast innehåller metoder, är den här gruppen med metoder resultatet av sökningen.
    • Annars är sökningen tvetydig och ett bindningstidsfel inträffar.

För medlemssökningar i andra typer än typparametrar och gränssnitt och medlemssökningar i gränssnitt som är strikt enkelarv (varje gränssnitt i arvskedjan har exakt noll eller ett direkt basgränssnitt) är effekten av uppslagsreglerna helt enkelt att härledda medlemmar döljer basmedlemmar med samma namn eller signatur. Sådana enarvsökningar är aldrig tvetydiga. De tvetydigheter som eventuellt kan uppstå vid medlemssökningar i flera arvsgränssnitt beskrivs i §18.4.6.

Obs! Den här fasen står bara för en typ av tvetydighet. Om medlemssökningen resulterar i en metodgrupp kan ytterligare användning av metodgruppen misslyckas på grund av tvetydighet, till exempel enligt beskrivningen i §12.6.4.1 och §12.6.6.2. slutkommentar

12.5.2 Bastyper

För medlemssökning anses en typ T ha följande bastyper:

  • Om T är object eller dynamichar du ingen T bastyp.
  • Om T är en enum_type är bastyperna T klasstyperna System.Enum, System.ValueTypeoch object.
  • Om T är en struct_type är bastyperna T klasstyperna System.ValueType och object.

    Obs! En nullable_value_type är en struct_type (§8.3.1). slutkommentar

  • Om T är en class_type är bastyperna T för basklasserna för T, inklusive klasstypen object.
  • Om T är en interface_type är bastyperna T T för basgränssnitten för och klasstypen object.
  • Om T är en array_type är bastyperna T klasstyper System.Array och object.
  • Om T är en delegate_type är bastyperna T klasstyperna System.Delegate och object.

12.6 Funktionsmedlemmar

12.6.1 Allmänt

Funktionsmedlemmar är medlemmar som innehåller körbara instruktioner. Funktionsmedlemmar är alltid medlemmar av typer och kan inte vara medlemmar i namnområden. C# definierar följande kategorier av funktionsmedlemmar:

  • Metoder
  • Egenskaper
  • Händelser
  • Indexerare
  • Användardefinierade operatörer
  • Instanskonstruktorer
  • Statiska konstruktorer
  • Slutförare

Med undantag för finalizers och statiska konstruktorer (som inte kan anropas explicit) körs -uttrycken i funktionsmedlemmar via funktionsmedlemsanrop. Den faktiska syntaxen för att skriva en funktionsmedlemsanrop beror på den specifika funktionsmedlemskategorin.

Argumentlistan (§12.6.2) för ett funktionsmedlemsanrop innehåller faktiska värden eller variabelreferenser för funktionsmedlemmens parametrar.

Anrop av generiska metoder kan använda typinferens för att fastställa vilken uppsättning typargument som ska skickas till metoden. Denna process beskrivs i §12.6.3.

Anrop av metoder, indexerare, operatorer och instanskonstruktorer använder överbelastningsmatchning för att avgöra vilken av en kandidatuppsättning funktionsmedlemmar som ska anropas. Denna process beskrivs i §12.6.4.

När en viss funktionsmedlem har identifierats vid bindningstid, eventuellt genom överbelastningsmatchning, beskrivs den faktiska körningsprocessen för att anropa funktionsmedlemmen i §12.6.6.

Obs! Följande tabell sammanfattar bearbetningen som sker i konstruktioner som omfattar de sex kategorier av funktionsmedlemmar som uttryckligen kan anropas. I tabellen , , , och anger uttryck som klassificerats som variabler eller värden anger T ett uttryck klassificerat som en typ, F är det enkla namnet på en metod och P är det enkla namnet på en egenskap.value yxe

Konstruera Exempel beskrivning
Metodanrop F(x, y) Överbelastningsupplösning används för att välja den bästa metoden F i den innehållande klassen eller structen. Metoden anropas med argumentlistan (x, y). Om metoden inte staticär är thisinstansuttrycket .
T.F(x, y) Överbelastningsupplösning används för att välja den bästa metoden F i klassen eller struct T. Ett bindningstidsfel uppstår om metoden inte staticär . Metoden anropas med argumentlistan (x, y).
e.F(x, y) Överlagringsupplösning används för att välja den bästa metoden F i klassen, struct eller gränssnittet som anges av typen e. Ett bindningstidsfel uppstår om metoden är static. Metoden anropas med instansuttrycket e och argumentlistan (x, y).
Egenskapsåtkomst P Get-accessorn för egenskapen P i den innehållande klassen eller struct anropas. Ett kompileringsfel inträffar om P det är skrivskyddat. Om P inte staticär är thisinstansuttrycket .
P = value Set-accessorn för egenskapen P i den innehållande klassen eller struct anropas med argumentlistan (value). Ett kompileringsfel inträffar om P det är skrivskyddat. Om P inte staticär är thisinstansuttrycket .
T.P Get-accessorn för egenskapen P i klassen eller struct T anropas. Ett kompileringsfel inträffar om P det inte static är eller om P det är skrivskyddat.
T.P = value Set-accessorn för egenskapen P i klassen eller struct T anropas med argumentlistan (value). Ett kompileringsfel inträffar om P det inte static är eller om P det är skrivskyddat.
e.P Get-accessorn för egenskapen P i klassen, struct eller gränssnittet som anges av typen av E anropas med instansuttrycket e. Ett bindningstidsfel uppstår om P är static eller om P är skrivskyddat.
e.P = value Set-accessorn för egenskapen P i klassen, struct eller gränssnittet som anges av typen av E anropas med instansuttrycket e och argumentlistan (value). Ett bindningstidsfel uppstår om P är static eller om P är skrivskyddat.
Händelseåtkomst E += value Tilläggsåtkomsten för händelsen E i den innehållande klassen eller struct anropas. Om E inte staticär är thisinstansuttrycket .
E -= value Borttagningsåtkomstorn för händelsen E i den innehållande klassen eller struct anropas. Om E inte staticär är thisinstansuttrycket .
T.E += value Tilläggsåtkomsten för händelsen E i klassen eller struct T anropas. Ett bindningstidsfel inträffar om E det inte staticär .
T.E -= value Borttagningsåtkomstorn för händelsen E i klassen eller struct T anropas. Ett bindningstidsfel inträffar om E det inte staticär .
e.E += value Tilläggsåtkomsten för händelsen E i klassen, struct eller gränssnittet som anges av typen av E anropas med instansuttrycket e. Ett bindningstidsfel inträffar om E är static.
e.E -= value Ta bort-accessorn för händelsen E i klassen, struct eller gränssnittet som anges av typen av E anropas med instansuttrycket e. Ett bindningstidsfel inträffar om E är static.
Indexerarens åtkomst e[x, y] Överlagringsmatchning används för att välja den bästa indexeraren i klassen, struct eller gränssnittet som anges av typen e. Indexerarens get-accessor anropas med instansuttrycket e och argumentlistan (x, y). Ett bindningstidsfel uppstår om indexeraren är skrivskyddad.
e[x, y] = value Överlagringsmatchning används för att välja den bästa indexeraren i klassen, struct eller gränssnittet som anges av typen e. Indexerarens uppsättningsåtkomst anropas med instansuttrycket e och argumentlistan (x, y, value). Ett bindningstidsfel uppstår om indexeraren är skrivskyddad.
Operatoranrop -x Överlagringsupplösning används för att välja den bästa unary-operatorn i klassen eller structen som anges av typen x. Den valda operatorn anropas med argumentlistan (x).
x + y Överlagringsupplösning tillämpas för att välja den bästa binära operatorn i klasserna eller structs som anges av typerna av x och y. Den valda operatorn anropas med argumentlistan (x, y).
Instanskonstruktoranrop new T(x, y) Överbelastningsmatchning tillämpas för att välja den bästa instanskonstruktorn i klassen eller structen T. Instanskonstruktorn anropas med argumentlistan (x, y).

slutkommentar

12.6.2 Argumentlistor

12.6.2.1 Allmänt

Varje funktionsmedlem och ombudsanrop innehåller en argumentlista som innehåller faktiska värden eller variabelreferenser för funktionsmedlemmens parametrar. Syntaxen för att ange argumentlistan för ett funktionsmedlemsanrop beror på funktionsmedlemskategorin:

  • För exempelvis konstruktorer, metoder, indexerare och ombud anges argumenten som en argument_list enligt beskrivningen nedan. För indexerare innehåller argumentlistan dessutom det uttryck som anges som rätt operand för tilldelningsoperatorn när du anropar den angivna åtkomsten.

    Obs! Det här ytterligare argumentet används inte för överbelastningsmatchning, bara under anrop av den angivna åtkomstorn. slutkommentar

  • För egenskaper är argumentlistan tom när du anropar get-accessorn och består av det uttryck som anges som rätt operand för tilldelningsoperatorn när du anropar den angivna accessorn.
  • För händelser består argumentlistan av det uttryck som anges som den högra operanden för operatorn += eller -= .
  • För användardefinierade operatorer består argumentlistan av den enskilda operanden för den unary operatorn eller de två operanderna för den binära operatorn.

Argumenten för egenskaper (§15.7) och händelser (§15.8) skickas alltid som värdeparametrar (§15.6.2.2). Argumenten för användardefinierade operatorer (§15.10) skickas alltid som värdeparametrar (§15.6.2.2) eller indataparametrar (§9.2.8). Argumenten för indexerare (§15.9) skickas alltid som värdeparametrar (§15.6.2.2), indataparametrar (§9.2.8) eller parametermatriser (§15.6.2.4). Utdata och referensparametrar stöds inte för dessa kategorier av funktionsmedlemmar.

Argumenten för en instanskonstruktor, metod, indexerare eller ombudsanrop anges som en argument_list:

argument_list
    : argument (',' argument)*
    ;

argument
    : argument_name? argument_value
    ;

argument_name
    : identifier ':'
    ;

argument_value
    : expression
    | 'in' variable_reference
    | 'ref' variable_reference
    | 'out' variable_reference
    ;

En argument_list består av ett eller flera argument, avgränsade med kommatecken. Varje argument består av en valfri argument_name följt av en argument_value. Ett argument med en argument_name kallas ett namngivet argument, medan ett argument utan argument_name är ett positionsargument.

Argument_value kan ha något av följande formulär:

  • Ett uttryck som anger att argumentet skickas som en värdeparameter eller omvandlas till en indataparameter och sedan skickas som det, enligt (§12.6.4.2 och beskrivs i §12.6.2.3.
  • Nyckelordet in följt av en variable_reference (§9.5), som anger att argumentet skickas som en indataparameter (§15.6.2.3.2). En variabel ska definitivt tilldelas (§9.4) innan den kan skickas som en indataparameter.
  • Nyckelordet ref följt av en variable_reference (§9.5), som anger att argumentet skickas som en referensparameter (§15.6.2.3.3). En variabel ska definitivt tilldelas (§9.4) innan den kan skickas som en referensparameter.
  • Nyckelordet out följt av en variable_reference (§9.5), som anger att argumentet skickas som en utdataparameter (§15.6.2.3.4). En variabel anses definitivt tilldelad (§9.4) efter ett funktionsmedlemsanrop där variabeln skickas som en utdataparameter.

Formuläret bestämmer parameteröverföringsläget för argumentet: värde, indata, referens respektive utdata. Men som nämnts ovan kan ett argument med värdeöverföringsläge omvandlas till ett med indataöverföringsläge.

Att skicka ett flyktigt fält (§15.5.4) som indata, utdata eller referensparameter orsakar en varning, eftersom fältet kanske inte behandlas som flyktigt av den anropade metoden.

12.6.2.2 Motsvarande parametrar

För varje argument i en argumentlista måste det finnas en motsvarande parameter i funktionsmedlemmen eller ombudet som anropas.

Parameterlistan som används i följande bestäms på följande sätt:

  • För virtuella metoder och indexerare som definierats i klasser väljs parameterlistan från den första deklarationen eller åsidosättningen av funktionsmedlemmen som hittades när den började med mottagarens statiska typ och söker igenom dess basklasser.
  • För partiella metoder används parameterlistan för den definierande partiella metoddeklarationen.
  • För alla andra funktionsmedlemmar och ombud finns det bara en enda parameterlista, som är den som används.

Positionen för ett argument eller en parameter definieras som antalet argument eller parametrar som föregår det i argumentlistan eller parameterlistan.

Motsvarande parametrar för funktionsmedlemsargument upprättas på följande sätt:

  • Argument i argument_list för instanskonstruktorer, metoder, indexerare och ombud:
    • Ett positionsargument där en parameter förekommer på samma plats i parameterlistan motsvarar den parametern, såvida inte parametern är en parametermatris och funktionsmedlemmen anropas i dess expanderade formulär.
    • Ett positionsargument för en funktionsmedlem med en parametermatris som anropas i dess expanderade form, som inträffar vid eller efter positionen för parametermatrisen i parameterlistan, motsvarar ett element i parametermatrisen.
    • Ett namngivet argument motsvarar parametern med samma namn i parameterlistan.
    • För indexerare motsvarar det uttryck som anges som rätt operand för tilldelningsoperatorn den implicita value parametern för den angivna åtkomstdeklarationen när du anropar den angivna åtkomstgivaren.
  • För egenskaper finns det inga argument när du anropar get-accessorn. När du anropar den angivna åtkomstorn motsvarar uttrycket som anges som rätt operand för tilldelningsoperatorn den implicita värdeparametern för den angivna åtkomstdeklarationen.
  • För användardefinierade unary-operatorer (inklusive konverteringar) motsvarar den enda operanden den enda parametern i operatordeklarationen.
  • För användardefinierade binära operatorer motsvarar den vänstra operanden den första parametern och den högra operanden motsvarar den andra parametern i operatordeklarationen.
  • Ett namnlöst argument motsvarar ingen parameter när det är efter ett argument med namnet out-of-position eller ett namngivet argument som motsvarar en parametermatris.

    Obs! Detta förhindrar void M(bool a = true, bool b = true, bool c = true); att anropas av M(c: false, valueB);. Det första argumentet används utanför position (argumentet används i första positionen, men parametern med namnet c är i tredje position), så följande argument bör namnges. Med andra ord tillåts icke-avslutande namngivna argument endast när namnet och positionen resulterar i att samma motsvarande parameter hittas. slutkommentar

12.6.2.3 Körningsutvärdering av argumentlistor

Under körningsbearbetningen av ett funktionsmedlemsanrop (§12.6.6) utvärderas uttrycken eller variabelreferenserna för en argumentlista i ordning, från vänster till höger, enligt följande:

  • Om parameterns överföringsläge är värde för ett värdeargument

    • argumentuttrycket utvärderas och en implicit konvertering (§10.2) till motsvarande parametertyp utförs. Det resulterande värdet blir det initiala värdet för värdeparametern i funktionsmedlemsanropet.

    • Annars är parameterns överföringsläge indata. Om argumentet är en variabelreferens och det finns en identitetskonvertering (§10.2.2) mellan argumentets typ och parameterns typ blir den resulterande lagringsplatsen lagringsplatsen som representeras av parametern i funktionsmedlemsanropet. Annars skapas en lagringsplats med samma typ som motsvarande parameter. Argumentuttrycket utvärderas och en implicit konvertering (§10.2) till motsvarande parametertyp utförs. Det resulterande värdet lagras på lagringsplatsen. Lagringsplatsen representeras av indataparametern i funktionsmedlemsanropet.

      Exempel: Med följande deklarationer och metodanrop:

      static void M1(in int p1) { ... }
      int i = 10;
      M1(i);         // i is passed as an input argument
      M1(i + 5);     // transformed to a temporary input argument
      

      I metodanropet M1(i) i skickas själva som ett indataargument, eftersom det klassificeras som en variabel och har samma typ int som indataparametern. I metodanropet M1(i + 5) skapas en namnlös int variabel, initieras med argumentets värde och skickas sedan som ett indataargument. Se §12.6.4.2 och §12.6.4.4.

      slutexempel

  • För indata, utdata eller referensargument utvärderas variabelreferensen och den resulterande lagringsplatsen blir lagringsplatsen som representeras av parametern i funktionsmedlemsanropet. För indata eller referensargument ska variabeln definitivt tilldelas vid tidpunkten för metodanropet. Om variabelreferensen anges som ett utdataargument eller är ett matriselement i en reference_type utförs en körningskontroll för att säkerställa att elementtypen för matrisen är identisk med parametertypen. Om den här kontrollen misslyckas utlöses en System.ArrayTypeMismatchException .

Obs! Den här körningskontrollen krävs på grund av matriskovarians (§17.6). slutkommentar

Exempel: I följande kod

class Test
{
    static void F(ref object x) {...}

    static void Main()
    {
        object[] a = new object[10];
        object[] b = new string[10];
        F(ref a[0]); // Ok
        F(ref b[1]); // ArrayTypeMismatchException
    }
}

det andra anropet av F orsakar att en System.ArrayTypeMismatchException genereras eftersom den faktiska elementtypen b är string och inte object.

slutexempel

Metoder, indexerare och instanskonstruktorer kan deklarera sin rätt-mest-parameter som en parametermatris (§15.6.2.4). Sådana funktionsmedlemmar anropas antingen i sin normala form eller i sin utökade form beroende på vilket som är tillämpligt (§12.6.4.2):

  • När en funktionsmedlem med en parametermatris anropas i sin normala form ska argumentet för parametermatrisen vara ett enda uttryck som implicit är konvertibelt (§10.2) till parametermatristypen. I det här fallet fungerar parametermatrisen exakt som en värdeparameter.
  • När en funktionsmedlem med en parametermatris anropas i dess expanderade form ska anropet ange noll eller fler positionsargument för parametermatrisen, där varje argument är ett uttryck som implicit är konvertibelt (§10.2) till parametermatrisens elementtyp. 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.

Uttrycken för en argumentlista utvärderas alltid i textordning.

Exempel: Därför är exemplet

class Test
{
    static void F(int x, int y = -1, int z = -2) =>
        Console.WriteLine($"x = {x}, y = {y}, z = {z}");

    static void Main()
    {
        int i = 0;
        F(i++, i++, i++);
        F(z: i++, x: i++);
    }
}

genererar utdata

x = 0, y = 1, z = 2
x = 4, y = -1, z = 3

slutexempel

När en funktionsmedlem med en parametermatris anropas i sitt expanderade formulär med minst ett expanderat argument bearbetas anropet som om ett matrisskapande uttryck med en matrisinitierare (§12.8.17.5) infogades runt de expanderade argumenten. En tom matris skickas när det inte finns några argument för parametermatrisen. Det är ospecificerat om referensen som skickas är till en nyligen allokerad eller befintlig tom matris.

Exempel: Givet deklarationen

void F(int x, int y, params object[] args);

följande anrop av metodens expanderade form

F(10, 20, 30, 40);
F(10, 20, 1, "hello", 3.0);

motsvarar exakt

F(10, 20, new object[] { 30, 40 });
F(10, 20, new object[] { 1, "hello", 3.0 });

slutexempel

När argument utelämnas från en funktionsmedlem med motsvarande valfria parametrar skickas standardargumenten för funktionsmedlemsdeklarationen implicit. (Detta kan innebära att du skapar en lagringsplats enligt beskrivningen ovan.)

Obs! Eftersom dessa alltid är konstanta påverkas inte utvärderingen av återstående argument. slutkommentar

12.6.3 Typinferens

12.6.3.1 Allmänt

När en generisk metod anropas utan att ange typargument försöker en typinferensprocess härleda typargument för anropet. Förekomsten av typinferens gör det möjligt att använda en mer praktisk syntax för att anropa en generisk metod och gör att programmeraren kan undvika att ange redundant typinformation.

Exempel:

class Chooser
{
    static Random rand = new Random();

    public static T Choose<T>(T first, T second) =>
        rand.Next(2) == 0 ? first : second;
}

class A
{
    static void M()
    {
        int i = Chooser.Choose(5, 213); // Calls Choose<int>
        string s = Chooser.Choose("apple", "banana"); // Calls Choose<string>
    }
}

Genom typinferens bestäms typargumenten int och string från argumenten till metoden.

slutexempel

Typinferens sker som en del av bindningstidsbearbetningen av ett metodanrop (§12.8.10.2) och äger rum före överbelastningsmatchningssteget för anropet. När en viss metodgrupp anges i ett metodanrop och inga typargument anges som en del av metodanropet tillämpas typinferens på varje generisk metod i metodgruppen. Om typinferensen lyckas används de härledda typargumenten för att fastställa typerna av argument för efterföljande överbelastningsmatchning. Om överlagringsmatchning väljer en allmän metod som den som ska anropas används argumenten av den här typen som typargument för anropet. Om typinferensen för en viss metod misslyckas deltar inte den metoden i överbelastningsmatchning. Felet med typinferens i sig orsakar inte ett bindningstidsfel. Det leder dock ofta till ett bindningstidsfel när överlagringslösningen misslyckas med att hitta några tillämpliga metoder.

Om varje angivet argument inte motsvarar exakt en parameter i metoden (§12.6.2.2), eller om det finns en icke-valfri parameter utan motsvarande argument, misslyckas slutsatsdragningen omedelbart. Anta annars att den generiska metoden har följande signatur:

Tₑ M<X₁...Xᵥ>(T₁ p₁ ... Tₓ pₓ)

Med ett metodanrop av formuläret M(E₁ ...Eₓ) är uppgiften av typen slutsatsdragning att hitta unika typargument S₁...Sᵥ för var och en av typparametrarna X₁...Xᵥ så att anropet M<S₁...Sᵥ>(E₁...Eₓ) blir giltigt.

Processen för typinferens beskrivs nedan som en algoritm. En konform kompilator kan implementeras med en alternativ metod, förutsatt att den når samma resultat i alla fall.

Under inferensprocessen är varje typparameter Xᵢ antingen fast i en viss typ Sᵢ eller ofixerad med en associerad uppsättning gränser. Var och en av gränserna är av någon typ T. Inledningsvis är varje typvariabel Xᵢ ofixerad med en tom uppsättning gränser.

Typinferens sker i faser. Varje fas försöker härleda typargument för fler typvariabler baserat på resultaten från föregående fas. Den första fasen gör vissa inledande slutsatsdragningar av gränser, medan den andra fasen korrigerar typvariabler till specifika typer och härleder ytterligare gränser. Den andra fasen kan behöva upprepas ett antal gånger.

Obs! Typinferens används också i andra sammanhang, inklusive för konvertering av metodgrupper (§12.6.3.14) och för att hitta den bästa gemensamma typen av uttryck (§12.6.3.15). slutkommentar

12.6.3.2 Den första fasen

För vart och ett av metodargumenten Eᵢ:

  • Om Eᵢ är en anonym funktion görs en explicit parametertypinferens (§12.6.3.8) frånEᵢ tillTᵢ
  • Om det annars Eᵢ finns en typ U och motsvarande parameter är en värdeparameter (§15.6.2.2) görs en lägre slutsatsdragning (§12.6.3.10) frånU till.Tᵢ
  • Om det annars Eᵢ finns en typ U och motsvarande parameter är en referensparameter (§15.6.2.3.3) eller utdataparameter (§15.6.2.3.4) görs en exakt slutsatsdragning (§12.6.3.9) frånU till.Tᵢ
  • Annars, om Eᵢ har en typ U och motsvarande parameter är en indataparameter (§15.6.2.3.2) och Eᵢ är ett indataargument, görs en exakt slutsatsdragning (§12.6.3.9) frånU till.Tᵢ
  • Om Eᵢ den har en typ U och motsvarande parameter är en indataparameter (§15.6.2.3.2) görs annars en lägre bunden slutsats (§12.6.3.10) frånU till.Tᵢ
  • Annars görs ingen slutsatsdragning för det här argumentet.

12.6.3.3 Den andra fasen

Den andra fasen fortsätter på följande sätt:

  • Alla ofixade typvariabler Xᵢ som inte är beroende av (§12.6.3.6) är Xₑ fasta (§12.6.3.12).
  • Om det inte finns några sådana typvariabler, korrigeras Xᵢ alla variabler av typen ofix som alla följande undantag gäller för:
    • Det finns minst en typvariabel Xₑ som är beroende avXᵢ
    • Xᵢ har en icke-tom uppsättning gränser
  • Om det inte finns några sådana typvariabler och det fortfarande finns ofixerade typvariabler misslyckas typinferensen.
  • Annars, om det inte finns några ytterligare ofixerade typvariabler, lyckas typinferensen.
  • För alla argument Eᵢ med motsvarande parametertyp Tᵢ där utdatatyperna (§12.6.3.5) innehåller ofixerade typvariablerXₑ, men indatatyperna (§12.6.3.4) inte, görs en slutsatsdragning av utdatatyp (§12.6.3.7) frånEᵢ till.Tᵢ Sedan upprepas den andra fasen.

12.6.3.4 Indatatyper

Om E är en metodgrupp eller implicit typad anonym funktion och T är en ombudstyp eller en uttrycksträdstyp är alla parametertyper T av indatatyper avE med typ.T

12.6.3.5 Utdatatyper

Om E är en metodgrupp eller en anonym funktion och T är en ombudstyp eller uttrycksträdstyp är returtypen T en utdatatyp avE med typen .T

12.6.3.6 Beroende

En ofixerad typvariabel Xᵢ beror direkt på en ofixerad typvariabel Xₑ om för vissa argument Eᵥ med typ Xₑ Tᵥ inträffar i en indatatyp av Eᵥ med typ Tᵥ och Xᵢ inträffar i en utdatatyp av Eᵥ med typen Tᵥ.

Xₑberor påXᵢ om Xₑ beror direkt påXᵢ eller om Xᵢ är direktXᵥ beroende av och Xᵥ är beroende avXₑ. Således är "beroende av" den transitiva men inte reflexiva stängningen av "beror direkt på".

12.6.3.7 Slutsatsdragningar av utdatatyp

En slutsatsdragning av utdatatyp görs från ett uttryck E till en typ T på följande sätt:

  • Om E är en anonym funktion med härledd returtyp U (§12.6.3.13) och T är en ombudstyp eller uttrycksträdtyp med returtyp Tₓ, görs en lägre inferens (§12.6.3.10) frånU till.Tₓ
  • Annars, om E är en metodgrupp och T är en ombudstyp eller uttrycksträdstyp med parametertyper T₁...Tᵥ och returtyp Tₓ, och överlagringsmatchning av E med typerna T₁...Tᵥ ger en enda metod med returtypU, görs en lägre slutsatsdragning frånU till.Tₓ
  • Annars, om E är ett uttryck med typen U, görs en lägre slutsatsdragning frånU till.T
  • Annars görs inga slutsatsdragningar.

12.6.3.8 Explicita parametertypinferenser

En explicit parametertypsinferens görs från ett uttryck E till en typ T på följande sätt:

  • Om E är en explicit typ av anonym funktion med parametertyper U₁...Uᵥ och T är en trädtyp för ombud eller uttryck med parametertyper V₁...Vᵥ görs för varje Uᵢ exakt slutsatsdragning (§12.6.3.9) frånUᵢ till motsvarande .Vᵢ

12.6.3.9 Exakta slutsatsdragningar

En exakt slutsatsdragning från en typ U till en typ V görs på följande sätt:

  • Om V är en av de ofixadeXᵢ U läggs den till i uppsättningen med exakta gränser för .Xᵢ
  • Annars anges V₁...Vₑ och U₁...Uₑ bestäms genom att kontrollera om något av följande fall gäller:
    • V är en matristyp V₁[...] och U är en matristyp U₁[...] med samma rangordning
    • V är typen V₁? och U är typen U₁
    • V är en konstruerad typ C<V₁...Vₑ> och U är en konstruerad typ C<U₁...Uₑ>
      Om något av dessa fall gäller görs en exakt slutsatsdragning från var och en Uᵢ till motsvarande Vᵢ.
  • Annars görs inga slutsatsdragningar.

12.6.3.10 Lägre bundna slutsatsdragningar

En lägre slutsatsdragning från en typ U till en typ V görs på följande sätt:

  • Om V är en av de ofixadeXᵢ U läggs den till i uppsättningen med lägre gränser för .Xᵢ
  • Annars, om V är typen V₁? och U är typen U₁? , görs en lägre bunden slutsatsdragning från U₁ till V₁.
  • Annars anges U₁...Uₑ och V₁...Vₑ bestäms genom att kontrollera om något av följande fall gäller:
    • V är en matristyp V₁[...]och U är en matristyp U₁[...]med samma rangordning
    • V är en av IEnumerable<V₁>, ICollection<V₁>, IReadOnlyList<V₁>>eller IReadOnlyCollection<V₁> IList<V₁> och U är en endimensionell matristyp U₁[]
    • V är en konstruerad class, struct, interface eller delegate typ C<V₁...Vₑ> och det finns en unik typ C<U₁...Uₑ> som U (eller, om U är en typ parameter, dess effektiva basklass eller någon medlem i dess effektiva gränssnittsuppsättning) är identisk med, inherits från (direkt eller indirekt) eller implementerar (direkt eller indirekt) C<U₁...Uₑ>.
    • (Begränsningen "unikhet" innebär att i ärendegränssnittet C<T>{} class U: C<X>, C<Y>{}görs ingen slutsatsdragning vid slutsatsdragning från U till C<T> eftersom U₁ kan vara X eller Y.)
      Om något av dessa fall gäller görs en slutsats från var och Uᵢ en till motsvarande Vᵢ enligt följande:
    • Om Uᵢ det inte är känt att det är en referenstyp görs en exakt slutsatsdragning
    • Om är en matristyp görs annars U en slutsatsdragning med lägre bindning
    • Annars, om V är C<V₁...Vₑ> då slutsatsdragning beror på typparametern i-th för C:
      • Om den är covariant görs en lägre bunden slutsatsdragning .
      • Om det är kontravariant görs en övre gränsinferens .
      • Om det är invariant görs en exakt slutsatsdragning .
  • Annars görs inga slutsatsdragningar.

12.6.3.11 Övre bundna slutsatsdragningar

En övre bindnings slutsatsdragning från en typ U till en typ V görs på följande sätt:

  • Om V är en av de ofixadeXᵢ U läggs den till i uppsättningen med övre gränser för .Xᵢ
  • Annars anges V₁...Vₑ och U₁...Uₑ bestäms genom att kontrollera om något av följande fall gäller:
    • U är en matristyp U₁[...]och V är en matristyp V₁[...]med samma rangordning
    • U är en av IEnumerable<Uₑ>, ICollection<Uₑ>, IReadOnlyList<Uₑ>eller IReadOnlyCollection<Uₑ> IList<Uₑ> och V är en endimensionell matristyp Vₑ[]
    • U är typen U1? och V är typen V1?
    • Uär konstruerad klass, struct, gränssnitt eller ombudstyp C<U₁...Uₑ> och V är en eller delegate en class, struct, interface typ som är identical till, inherits från (direkt eller indirekt), eller implementerar (direkt eller indirekt) en unik typC<V₁...Vₑ>
    • (Begränsningen "unikhet" innebär att med tanke på ett gränssnitt C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}görs ingen slutsatsdragning när du härleder från C<U₁> till V<Q>. Slutsatsdragningar görs inte från U₁ till antingen X<Q> eller Y<Q>.)
      Om något av dessa fall gäller görs en slutsats från var och Uᵢ en till motsvarande Vᵢ enligt följande:
    • Om Uᵢ det inte är känt att det är en referenstyp görs en exakt slutsatsdragning
    • Om är en matristyp görs annars V en övre gränsinferens
    • Annars, om U är C<U₁...Uₑ> då slutsatsdragning beror på typparametern i-th för C:
      • Om den är covariant görs en övre gränsinferens .
      • Om det är kontravariant görs en lägre bunden slutsatsdragning .
      • Om det är invariant görs en exakt slutsatsdragning .
  • Annars görs inga slutsatsdragningar.

12.6.3.12 Fixering

En ofixerad typvariabel Xᵢ med en uppsättning gränser har åtgärdats på följande sätt:

  • Uppsättningen kandidattyper Uₑ börjar som uppsättningen med alla typer i uppsättningen med gränser för Xᵢ.
  • Varje bindning Xᵢ till granskas i tur och ordning: För varje exakt bunden U av Xᵢ alla typer Uₑ som inte är identiska med U tas bort från kandidatuppsättningen. För varje lägre gräns U av Xᵢ alla typer Uₑ som det inte finns någon implicit konvertering från U tas bort från kandidatuppsättningen. För varje övre gräns U av Xᵢ alla typer Uₑ som det inte finns någon implicit konvertering till tas U bort från kandidatuppsättningen.
  • Om det bland de återstående kandidattyperna Uₑ finns en unik typ V som det finns en implicit konvertering till från alla andra kandidattyper, Xᵢ så är den fast på V.
  • Annars misslyckas typinferensen.

12.6.3.13 Uppskjuten returtyp

Den härledda returtypen för en anonym funktion F används vid typinferens och överlagringsmatchning. Den härledda returtypen kan bara fastställas för en anonym funktion där alla parametertyper är kända, antingen för att de uttryckligen anges, tillhandahålls via en anonym funktionskonvertering eller härleds under typinferens på en omslutande generisk metodanrop.

Den här här typen av härledd effektiv retur bestäms på följande sätt:

  • Om brödtexten F i är ett uttryck som har en typ är den härledda effektiva returtypen F den typ av uttryck som används.
  • Om brödtexten F i är ett block och uppsättningen uttryck i blockets return -uttryck har en bästa gemensamma typ T (§12.6.3.15), är Tden härledda effektiva returtypen F .
  • Annars kan en effektiv returtyp inte härledas för F.

Den här här typen av uppskjuten retur bestäms på följande sätt:

  • Om F är asynkront och brödtexten F i antingen är ett uttryck klassificerat som ingenting (§12.2), eller ett block där inga return uttryck har uttryck, är «TaskType» den härledda returtypen (§15.15.1).
  • Om F är asynkron och har en härledd effektiv returtyp Tär «TaskType»<T>»den härledda returtypen (§15.15.1).
  • Om F är icke-asynkron och har en härledd effektiv returtyp Tär Tden härledda returtypen .
  • Annars går det inte att härleda en returtyp för F.

Exempel: Som ett exempel på typinferens som involverar anonyma funktioner bör du överväga den Select tilläggsmetod som deklarerats System.Linq.Enumerable i klassen:

namespace System.Linq
{
    public static class Enumerable
    {
        public static IEnumerable<TResult> Select<TSource,TResult>(
            this IEnumerable<TSource> source,
            Func<TSource,TResult> selector)
        {
            foreach (TSource element in source)
            {
                yield return selector(element);
            }
        }
   }
}

System.Linq Förutsatt att namnområdet importerades med ett using namespace direktiv och en klass Customer med en Name egenskap av typen string, Select kan metoden användas för att välja namnen på en lista över kunder:

List<Customer> customers = GetCustomerList();
IEnumerable<string> names = customers.Select(c => c.Name);

Anrop av tilläggsmetod (§12.8.10.3) av Select bearbetas genom att anropet skrivs om till en statisk metodanrop:

IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);

Eftersom typargument inte uttryckligen angavs används typinferens för att härleda typargumenten. Först är kundargumentet relaterat till källparametern och härleds TSource till att vara Customer. Sedan, med hjälp av den inferensprocess för anonym funktionstyp som beskrivs ovan, c ges typen Customer, och uttrycket c.Name är relaterat till returtypen för väljareparametern, vilket härleder TResult till att vara string. Anropet motsvarar således

Sequence.Select<Customer,string>(customers, (Customer c) => c.Name)

och resultatet är av typen IEnumerable<string>.

I följande exempel visas hur inferens av anonym funktionstyp gör att typinformation kan "flöda" mellan argument i en allmän metodanrop. Med följande metod och anrop:

class A
{
    static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2)
    {
        return f2(f1(value));
    }

    static void M()
    {
        double hours = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalHours);
    }
}

typinferens för anropet fortsätter enligt följande: Först är argumentet "1:15:30" relaterat till värdeparametern, vilket X innebär att det är sträng. Sedan får parametern för den första anonyma funktionen, s, den härledda typen stringoch uttrycket TimeSpan.Parse(s) är relaterat till returtypen för f1, som härleds Y System.TimeSpantill . Slutligen får parametern för den andra anonyma funktionen, t, den härledda typen System.TimeSpanoch uttrycket t.TotalHours är relaterat till returtypen för f2, som härleds Z doubletill . Resultatet av anropet är därför av typen double.

slutexempel

12.6.3.14 Typinferens för konvertering av metodgrupper

Liknande anrop av generiska metoder ska typinferens också tillämpas när en metodgrupp M som innehåller en generisk metod konverteras till en viss delegattyp D (§10.8). Givet en metod

Tₑ M<X₁...Xᵥ>(T₁ x₁ ... Tₑ xₑ)

och den metodgrupp M som tilldelas till ombudstypen D är uppgiften av typen slutsatsdragning att hitta typargument S₁...Sᵥ så att uttrycket:

M<S₁...Sᵥ>

blir kompatibel (§20.2) med D.

Till skillnad från typinferensalgoritmen för generiska metodanrop finns det i det här fallet bara argumenttyper, inga argumentuttryck. I synnerhet finns det inga anonyma funktioner och därför inget behov av flera inferensfaser.

I stället anses alla Xᵢ vara ofixerade och en slutsatsdragning med lägre bindning görs från varje argumenttyp D Uₑ till motsvarande parametertyp Tₑ för .M Xᵢ Om inga gränser hittades misslyckas typinferensen. Annars är alla Xᵢ fasta till motsvarande Sᵢ, vilket är resultatet av typinferens.

12.6.3.15 Hitta den vanligaste typen av uttryck

I vissa fall måste en vanlig typ härledas för en uppsättning uttryck. I synnerhet finns elementtyperna för implicit inskrivna matriser och returtyperna av anonyma funktioner med blockkroppar på det här sättet.

Den vanligaste typen för en uppsättning uttryck bestäms E₁...Eᵥ på följande sätt:

  • En ny variabel X av typen ofix introduceras.
  • För varje uttryck Ei utförs en inferens av utdatatyp (§12.6.3.7) från det till X.
  • X är fast (§12.6.3.12), om möjligt, och den resulterande typen är den bästa gemensamma typen.
  • Annars misslyckas slutsatsdragningen.

Obs! Intuitivt motsvarar den här slutsatsdragningen att anropa en metod void M<X>(X x₁ ... X xᵥ) med Eᵢ som argument och härleda X. slutkommentar

12.6.4 Överbelastningsupplösning

12.6.4.1 Allmänt

Överbelastningsmatchning är en bindningstidsmekanism för att välja den bästa funktionsmedlemmen att anropa givet en argumentlista och en uppsättning kandidatfunktionsmedlemmar. Överbelastningsmatchning väljer den funktionsmedlem som ska anropas i följande distinkta kontexter i C#:

  • Anrop av en metod med namnet i en invocation_expression (§12.8.10).
  • Anrop av en instanskonstruktor med namnet i en object_creation_expression (§12.8.17.2).
  • Anrop av en indexerare via en element_access (§12.8.12).
  • Anrop av en fördefinierad eller användardefinierad operator som refereras i ett uttryck (§12.4.4 och §12.4.5).

Var och en av dessa kontexter definierar uppsättningen kandidatfunktionsmedlemmar och listan över argument på sitt eget unika sätt. Till exempel innehåller uppsättningen kandidater för ett metodanrop inte metoder som markerats som åsidosättning (§12.5), och metoder i en basklass är inte kandidater om någon metod i en härledd klass är tillämplig (§12.8.10.2).

När kandidatfunktionens medlemmar och argumentlistan har identifierats är valet av den bästa funktionsmedlemmen detsamma i alla fall:

  • För det första reduceras uppsättningen kandidatfunktionsmedlemmar till de funktionsmedlemmar som gäller för den angivna argumentlistan (§12.6.4.2). Om den här reducerade uppsättningen är tom uppstår ett kompileringsfel.
  • Sedan finns den bästa funktionsmedlemmen från uppsättningen med tillämpliga kandidatfunktionsmedlemmar. Om uppsättningen endast innehåller en funktionsmedlem är funktionsmedlemmen den bästa funktionsmedlemmen. Annars är den bästa funktionsmedlemmen den funktionsmedlem som är bättre än alla andra funktionsmedlemmar med avseende på den angivna argumentlistan, förutsatt att varje funktionsmedlem jämförs med alla andra funktionsmedlemmar som använder reglerna i §12.6.4.3. Om det inte finns exakt en funktionsmedlem som är bättre än alla andra funktionsmedlemmar är funktionsmedlemsanropet tvetydigt och ett bindningstidsfel inträffar.

Följande underetiketter definierar de exakta betydelserna för de villkor som gäller funktionsmedlem och bättre funktionsmedlem.

12.6.4.2 Tillämplig funktionsmedlem

En funktionsmedlem sägs vara en tillämplig funktionsmedlem med avseende på en argumentlista A när allt av följande är sant:

  • Varje argument i A motsvarar en parameter i funktionsmedlemsdeklarationen enligt beskrivningen i §12.6.2.2, högst ett argument motsvarar varje parameter, och alla parametrar som inget argument motsvarar är en valfri parameter.
  • För varje argument i Aär parameteröverföringsläget för argumentet identiskt med parameteröverföringsläget för motsvarande parameter, och
    • för en värdeparameter eller en parametermatris finns det en implicit konvertering (§10.2) från argumentuttrycket till typen av motsvarande parameter, eller
    • för en referens- eller utdataparameter finns det en identitetskonvertering mellan typen av argumentuttryck (om någon) och typen av motsvarande parameter, eller
    • för en indataparameter när motsvarande argument har in modifieraren, finns det en identitetskonvertering mellan typen av argumentuttryck (om någon) och typen av motsvarande parameter, eller
    • för en indataparameter när motsvarande argument utelämnar in modifieraren, finns det en implicit konvertering (§10.2) från argumentuttrycket till typen av motsvarande parameter.

För en funktionsmedlem som innehåller en parametermatris, om funktionsmedlemmen är tillämplig enligt ovanstående regler, sägs den vara tillämplig i sin normala form. Om en funktionsmedlem som innehåller en parametermatris inte är tillämplig i sin normala form kan funktionsmedlemmen i stället vara tillämplig i dess utökade form:

  • Det expanderade formuläret skapas genom att parametermatrisen i funktionsmedlemsdeklarationen ersätts med noll eller fler värdeparametrar av elementtypen för parametermatrisen så att antalet argument i argumentlistan A matchar det totala antalet parametrar. Om A har färre argument än antalet fasta parametrar i funktionsmedlemsdeklarationen kan inte funktionsmedlemmens utökade form konstrueras och är därför inte tillämplig.
  • I annat fall gäller det expanderade formuläret om för varje argument i Aär något av följande sant:
    • parameteröverföringsläget för argumentet är identiskt med parameteröverföringsläget för motsvarande parameter, och
      • för en parameter med fast värde eller en värdeparameter som skapats av expansionen, finns en implicit konvertering (§10.2) från argumentuttrycket till typen av motsvarande parameter, eller
      • för en bireferensparameter är typen av argumentuttryck identisk med typen av motsvarande parameter.
    • parameter-passing-läget för argumentet är värde och parameteröverföringsläget för motsvarande parameter är indata, och en implicit konvertering (§10.2) finns från argumentuttrycket till typen av motsvarande parameter

När den implicita konverteringen från argumenttypen till parametertypen för en indataparameter är en dynamisk implicit konvertering (§10.2.10) är resultatet odefinierat.

Exempel: Med följande deklarationer och metodanrop:

public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }
public static void M2(in int p1) { ... }
public static void Test()
{
    int i = 10; uint ui = 34U;

    M1(in i);   // M1(in int) is applicable
    M1(in ui);  // no exact type match, so M1(in int) is not applicable
    M1(i);      // M1(int) and M1(in int) are applicable
    M1(i + 5);  // M1(int) and M1(in int) are applicable
    M1(100u);   // no implicit conversion exists, so M1(int) is not applicable

    M2(in i);   // M2(in int) is applicable
    M2(i);      // M2(in int) is applicable
    M2(i + 5);  // M2(in int) is applicable
}

slutexempel

  • En statisk metod är endast tillämplig om metodgruppen är resultatet av en simple_name eller en member_access genom en typ.
  • En instansmetod gäller endast om metodgruppen är resultatet av en simple_name, en member_access via en variabel eller ett värde eller en base_access.
    • Om metodgruppen är resultatet av en simple_name är en instansmetod endast tillämplig om this åtkomst tillåts §12.8.14.
  • När metodgruppen är resultatet av en member_access som kan ske antingen via en instans eller en typ enligt beskrivningen i §12.8.7.2 gäller både instanser och statiska metoder.
  • En allmän metod vars typargument (uttryckligen anges eller härleds) uppfyller inte alla deras begränsningar är inte tillämpligt.
  • I samband med en metodgruppkonvertering ska det finnas en identitetskonvertering (§10.2.2) eller en implicit referenskonvertering (§10.2.8) från metodens returtyp till ombudets returtyp. Annars är kandidatmetoden inte tillämplig.

12.6.4.3 Bättre funktionsmedlem

För att fastställa den bättre funktionsmedlemmen skapas en avskalad argumentlista A som bara innehåller argumentuttrycken själva i den ordning de visas i den ursprungliga argumentlistan och utelämnar alla out argument eller ref argument.

Parameterlistor för var och en av kandidatfunktionsmedlemmarna skapas på följande sätt:

  • Det expanderade formuläret används om funktionsmedlemmen endast var tillämplig i det expanderade formuläret.
  • Valfria parametrar utan motsvarande argument tas bort från parameterlistan
  • Referens- och utdataparametrar tas bort från parameterlistan
  • Parametrarna sorteras om så att de sker på samma position som motsvarande argument i argumentlistan.

Givet en argumentlista A med en uppsättning argumentuttryck och två tillämpliga funktionsmedlemmar Mᵥ {E₁, E₂, ..., Eᵥ} och Mₓ med parametertyper {P₁, P₂, ..., Pᵥ} och {Q₁, Q₂, ..., Qᵥ}, Mᵥ definieras som en bättre funktionsmedlem än Mₓ om

  • för varje argument är den implicita konverteringen från Eᵥ till Qᵥ inte bättre än den implicita konverteringen från Eᵥ till Pᵥ, och
  • för minst ett argument är konverteringen från Eᵥ till Pᵥ bättre än konverteringen från Eᵥ till Qᵥ.

Om parametertypsekvenserna {P₁, P₂, ..., Pᵥ} och är likvärdiga (dvs. var Pᵢ och {Q₁, Q₂, ..., Qᵥ} en har en identitetskonvertering till motsvarande Qᵢ) tillämpas följande regler för att fastställa den bättre funktionsmedlemmen.

  • Om Mᵢ är en icke-generisk metod och Mₑ är en allmän metod är den Mᵢ bättre än Mₑ.
  • Annars, om Mᵢ är tillämpligt i sin normala form och Mₑ har en params-matris och endast är tillämplig i dess expanderade form, Mᵢ så är bättre än Mₑ.
  • Annars, om båda metoderna har params-matriser och endast är tillämpliga i deras expanderade former, och om params-matrisen Mᵢ i har färre element än params-matrisen i Mₑ, är det Mᵢ bättre än Mₑ.
  • Annars, om Mᵥ har mer specifika parametertyper än Mₓ, är det Mᵥ bättre än Mₓ. Let {R1, R2, ..., Rn} and {S1, S2, ..., Sn} represent the uninstantiated and unexpanded parameter types of Mᵥ and Mₓ. Mᵥ's parametertyper är mer specifika än Mₓs om för varje parameter Rx inte är mindre specifik än Sx, och för minst en parameter Rx är mer specifik än Sx:
    • En typparameter är mindre specifik än en parameter av typen inte.
    • Rekursivt är en konstruerad typ mer specifik än en annan konstruerad typ (med samma antal typargument) om minst ett typargument är mer specifikt och inget typargument är mindre specifikt än motsvarande typargument i det andra.
    • En matristyp är mer specifik än en annan matristyp (med samma antal dimensioner) om elementtypen för den första är mer specifik än elementtypen för den andra.
  • Om en medlem annars är en icke-lyftad operatör och den andra är en lyftad operatör är den icke-lyftade bättre.
  • Om ingen funktionsmedlem Mᵥ visade sig vara bättre och alla parametrar i har ett motsvarande argument, medan standardargument måste ersättas med minst en valfri parameter i Mₓ, är det Mᵥ bättre än Mₓ.
  • Om för minst en parameter Mᵥ använder det bättre parameter-passing-valet (§12.6.4.4) än motsvarande parameter i Mₓ och ingen av de parametrar som Mₓ används är det bättre parameteröverföringsalternativet än Mᵥ, Mᵥ bättre än Mₓ.
  • Annars är ingen funktionsmedlem bättre.

12.6.4.4 Bättre parameteröverföringsläge

Det är tillåtet att ha motsvarande parametrar i två överlagrade metoder som endast skiljer sig åt i parameteröverföringsläget, förutsatt att en av de två parametrarna har värdeöverföringsläge enligt följande:

public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }

Givet int i = 10;, enligt §12.6.4.2, anropar M1(i) och M1(i + 5) resulterar i att båda överbelastningarna är tillämpliga. I sådana fall är metoden med värdets parameteröverföringsläge det bättre valet för parameteröverföringsläge.

Obs! Det finns inget sådant val för argument för indata, utdata eller referensöverföringslägen, eftersom dessa argument endast matchar exakt samma parameteröverföringslägen. slutkommentar

12.6.4.5 Bättre konvertering från uttryck

En implicit konvertering C₁ som konverterar från ett uttryck E till en typ T₁och en implicit konvertering C₂ som konverterar från ett uttryck E till en typ C₁ T₂är en bättre konvertering än C₂ om något av följande gäller:

  • E matchar T₁ exakt och E matchar T₂ inte exakt (§12.6.4.6)
  • E exakt matchar både eller inget av T₁ och T₂, och T₁ är ett bättre konverteringsmål än T₂ (§12.6.4.7)
  • E är en metodgrupp (§12.2), T₁ är kompatibel (§20.4) med den enskilt bästa metoden från metodgruppen för konvertering C₁och T₂ är inte kompatibel med den enda bästa metoden från metodgruppen för konvertering C₂

12.6.4.6 Exakt matchande uttryck

Givet ett uttryck E och en typ E Tmatchar T exakt om något av följande gäller:

  • E har en typ Soch det finns en identitetskonvertering från S till T
  • E är en anonym funktion, T är antingen en ombudstyp D eller en uttrycksträdstyp Expression<D> och något av följande gäller:
    • Det finns en härledd returtyp X för i kontexten för E parameterlistan D (§12.6.3.12) och det finns en identitetskonvertering från X returtypen för D
    • E är en async lambda utan returvärde och D har en returtyp som inte är generisk «TaskType»
    • Antingen E är icke-asynkron och D har en returtyp Y eller E är asynkron och D har en returtyp «TaskType»<Y>(§15.15.1), och något av följande gäller:
      • Brödtexten E i är ett uttryck som exakt matchar Y
      • Brödtexten E i är ett block där varje retursats returnerar ett uttryck som exakt matchar Y

12.6.4.7 Bättre konverteringsmål

Givet två typer T₁ och T₂, T₁ är ett bättre konverteringsmål än T₂ om något av följande gäller:

  • En implicit konvertering från T₁ till T₂ finns och ingen implicit konvertering från T₂ till T₁ finns
  • T₁ är «TaskType»<S₁>(§15.15.1), T₂ är «TaskType»<S₂>, och S₁ är ett bättre konverteringsmål än S₂
  • T₁ är «TaskType»<S₁>(§15.15.1), T₂ är «TaskType»<S₂>, och T₁ är mer specialiserad än T₂
  • T₁ är S₁ eller S₁? där S₁ är en signerad integraltyp och T₂ är S₂ eller S₂? där S₂ är en osignerad integraltyp. Specifikt:
    • S₁ är sbyte och S₂ är byte, ushort, uint, eller ulong
    • S₁ är short och S₂ är ushort, uint, eller ulong
    • S₁ är int och S₂ är uint, eller ulong
    • S₁ är long och S₂ är ulong

12.6.4.8 Överlagring i generiska klasser

Obs! Även om signaturer som deklarerats ska vara unika (§8.6), är det möjligt att ersättning av typargument resulterar i identiska signaturer. I en sådan situation väljer överlagringslösningen de mest specifika (§12.6.4.3) av de ursprungliga signaturerna (före ersättning av typargument), om det finns och rapporterar annars ett fel. slutkommentar

Exempel: I följande exempel visas överlagringar som är giltiga och ogiltiga enligt den här regeln:

public interface I1<T> { ... }
public interface I2<T> { ... }

public abstract class G1<U>
{
    public abstract int F1(U u);           // Overload resolution for G<int>.F1
    public abstract int F1(int i);         // will pick non-generic

    public abstract void F2(I1<U> a);      // Valid overload
    public abstract void F2(I2<U> a);
}

abstract class G2<U,V>
{
    public abstract void F3(U u, V v);     // Valid, but overload resolution for
    public abstract void F3(V v, U u);     // G2<int,int>.F3 will fail

    public abstract void F4(U u, I1<V> v); // Valid, but overload resolution for
    public abstract void F4(I1<V> v, U u); // G2<I1<int>,int>.F4 will fail

    public abstract void F5(U u1, I1<V> v2);   // Valid overload
    public abstract void F5(V v1, U u2);

    public abstract void F6(ref U u);      // Valid overload
    public abstract void F6(out V v);
}

slutexempel

12.6.5 Kompileringstidskontroll av dynamisk medlemsanrop

Även om överbelastningsupplösningen för en dynamiskt bunden åtgärd sker vid körning, är det ibland möjligt att vid kompileringstillfället känna till listan över funktionsmedlemmar som en överlagring ska väljas från:

  • För ett ombudsanrop (§12.8.10.4) är listan en enskild funktionsmedlem med samma parameterlista som delegate_type för anropet
  • För ett metodanrop (§12.8.10.2) på en typ, eller på ett värde vars statiska typ inte är dynamisk, är uppsättningen tillgängliga metoder i metodgruppen känd vid kompileringstid.
  • För ett objektskapandeuttryck (§12.8.17.2) är uppsättningen tillgängliga konstruktorer i typen känd vid kompileringstid.
  • För indexerareåtkomst (§12.8.12.3) är uppsättningen tillgängliga indexerare i mottagaren känd vid kompileringstid.

I dessa fall utförs en begränsad kompileringstidskontroll på varje medlem i den kända uppsättningen funktionsmedlemmar för att se om det kan vara känt att vissa aldrig anropas vid körning. För varje funktionsmedlem F skapas en modifierad parameter och en argumentlista:

  • Först, om F är en generisk metod och typargument angavs, ersätts de med typparametrarna i parameterlistan. Men om typargument inte har angetts sker ingen sådan ersättning.
  • Sedan genereras alla parametrar vars typ är öppen (d.v.s. innehåller en typparameter, se §8.4.3) tillsammans med motsvarande parametrar.

För F att kontrollen ska kunna genomföras skall alla följande gälla:

  • Den ändrade parameterlistan för F gäller för den ändrade argumentlistan enligt §12.6.4.2.
  • Alla konstruerade typer i listan över ändrade parametrar uppfyller sina begränsningar (§8.4.5).
  • Om typparametrarna F för ersattes i steget ovan uppfylls deras begränsningar.
  • Om F är en statisk metod ska metodgruppen inte ha resulterat från en member_access vars mottagare vid kompileringstid är en variabel eller ett värde.
  • Om F är en instansmetod ska metodgruppen inte ha resulterat från en member_access vars mottagare vid kompileringstiden är en typ.

Om ingen kandidat klarar det här testet uppstår ett kompileringsfel.

12.6.6 Funktionsmedlemsanrop

12.6.6.1 Allmänt

Den här underklienten beskriver den process som sker vid körning för att anropa en viss funktionsmedlem. Det antas att en bindningstidsprocess redan har fastställt vilken medlem som ska anropas, eventuellt genom att tillämpa överbelastningsmatchning på en uppsättning kandidatfunktionsmedlemmar.

För att beskriva anropsprocessen delas funktionsmedlemmar in i två kategorier:

  • Statiska funktionsmedlemmar. Det här är statiska metoder, statiska egenskapsåtkomster och användardefinierade operatorer. Statiska funktionsmedlemmar är alltid icke-virtuella.
  • Instansfunktionsmedlemmar. Det här är instansmetoder, instanskonstruktorer, instansegenskapsåtkomster och indexerare. Instansfunktionsmedlemmar är antingen icke-virtuella eller virtuella och anropas alltid på en viss instans. Instansen beräknas av ett instansuttryck och blir tillgänglig inom funktionsmedlemmen som this (§12.8.14). För en instanskonstruktor anses instansuttrycket vara det nyligen allokerade objektet.

Körningsbearbetningen av ett funktionsmedlemsanrop består av följande steg, där M är funktionsmedlemmen och, om M är en instansmedlem, E är instansuttrycket:

  • Om M är en statisk funktionsmedlem:

    • Argumentlistan utvärderas enligt beskrivningen i §12.6.2.
    • M anropas.
  • Annars, om typen av E är en värdetyp V, och M deklareras eller åsidosätts i V:

    • E utvärderas. Om den här utvärderingen orsakar ett undantag körs inga ytterligare steg. För en instanskonstruktor består den här utvärderingen av att allokera lagring (vanligtvis från en körningsstack) för det nya objektet. I det här fallet E klassificeras som en variabel.
    • Om E inte klassificeras som en variabel, eller om V inte är en skrivskyddad structtyp (§16.2.2), och E är en av:
      • en indataparameter (§15.6.2.3.2), eller
      • ett readonly fält (§15.5.3), eller
      • en readonly referensvariabel eller retur (§9.7),

    sedan skapas en tillfällig lokal variabel av Etypen och värdet E för tilldelas till variabeln. E omklassificeras sedan som en referens till den tillfälliga lokala variabeln. Den tillfälliga variabeln är tillgänglig som this inom M, men inte på något annat sätt. Det är alltså bara när E som kan skrivas är det möjligt för anroparen att observera de ändringar som M gör i this.

    • Argumentlistan utvärderas enligt beskrivningen i §12.6.2.
    • M anropas. Variabeln som refereras av E blir variabeln som refereras av this.
  • Annars:

    • E utvärderas. Om den här utvärderingen orsakar ett undantag körs inga ytterligare steg.
    • Argumentlistan utvärderas enligt beskrivningen i §12.6.2.
    • Om typen av E är en value_type utförs en boxningskonvertering (§10.2.9) för att konvertera E till en class_type och E anses vara av den class_type i följande steg. Om value_type är en enum_type är System.Enum; class_type annars System.ValueType.
    • Värdet E för är markerat för att vara giltigt. Om värdet E för är null genereras en System.NullReferenceException och inga ytterligare steg körs.
    • Den funktionsmedlemsimplementering som ska anropas bestäms:
      • Om bindningstidstypen E för är ett gränssnitt är funktionsmedlemmen som ska anropas implementeringen av M som tillhandahålls av körningstypen för den instans som refereras av E. Den här funktionsmedlemmen bestäms genom att tillämpa reglerna för gränssnittsmappning (§18.6.5) för att fastställa implementeringen av M tillhandahållen av körningstypen för instansen som refereras av E.
      • Annars, om M är en virtuell funktionsmedlem, är funktionsmedlemmen som ska anropas implementeringen av M som tillhandahålls av körningstypen för den instans som refereras av E. Den här funktionsmedlemmen bestäms genom att tillämpa reglerna för att fastställa den mest härledda implementeringen (§15.6.4) M med avseende på körningstypen för den instans som refereras av E.
      • Annars M är en icke-virtuell funktionsmedlem och funktionsmedlemmen som ska anropas är M sig själv.
    • Implementeringen av funktionsmedlem som fastställs i steget ovan anropas. Objektet som refereras av E blir det objekt som refereras till av detta.

Resultatet av anropet av en instanskonstruktor (§12.8.17.2) är det värde som skapas. Resultatet av anropet av någon annan funktionsmedlem är värdet, om något, som returneras (§13.10.5) från dess kropp.

12.6.6.2 Anrop på boxade instanser

En funktionsmedlem som implementeras i en value_type kan anropas via en boxad instans av den value_type i följande situationer:

  • När funktionsmedlemmen är en åsidosättning av en metod som ärvs från typen class_type och anropas via ett instansuttryck för den class_type.

    Obs! Class_type kommer alltid att vara en av System.Object, System.ValueType eller System.Enumslutkommentar

  • När funktionsmedlemmen är en implementering av en gränssnittsfunktionsmedlem och anropas via ett instansuttryck för en interface_type.
  • När funktionsmedlemmen anropas via ett ombud.

I dessa situationer anses den boxade instansen innehålla en variabel för value_type, och den här variabeln blir variabeln som refereras av detta i funktionsmedlemsanropet.

Obs! I synnerhet innebär det att när en funktionsmedlem anropas på en boxad instans är det möjligt för funktionsmedlemmen att ändra värdet i den inramade instansen. slutkommentar

12.7 Dekonstruktion

Dekonstruktion är en process där ett uttryck omvandlas till en tuppeln av enskilda uttryck. Dekonstruktion används när målet för en enkel tilldelning är ett tupppeluttryck, för att hämta värden som ska tilldelas till vart och ett av tuppelns element.

Ett uttryck E dekonstrueras till ett tupppeluttryck med n element på följande sätt:

  • Om E är ett tupppeluttryck med n element är resultatet av dekonstruktion själva uttrycket E .
  • Annars, om E har en tuppeln typ (T1, ..., Tn) med n element, E sedan utvärderas till en tillfällig variabel __v, och resultatet av dekonstruktion är uttrycket (__v.Item1, ..., __v.Itemn).
  • Annars utvärderas uttrycket om uttrycket E.Deconstruct(out var __v1, ..., out var __vn) vid kompileringstid matchar en unik instans eller tilläggsmetod, och resultatet av dekonstruktionen är uttrycket (__v1, ..., __vn). En sådan metod kallas för en dekonstruktion.
  • Annars E kan inte dekonstrueras.

__v Här och __v1, ..., __vn referera till annars osynliga och otillgängliga tillfälliga variabler.

Obs! Ett typuttryck dynamic kan inte dekonstrueras. slutkommentar

12.8 Primära uttryck

12.8.1 Allmänt

Primära uttryck är de enklaste uttrycksformerna.

primary_expression
    : primary_no_array_creation_expression
    | array_creation_expression
    ;

primary_no_array_creation_expression
    : literal
    | interpolated_string_expression
    | simple_name
    | parenthesized_expression
    | tuple_expression
    | member_access
    | null_conditional_member_access
    | invocation_expression
    | element_access
    | null_conditional_element_access
    | this_access
    | base_access
    | post_increment_expression
    | post_decrement_expression
    | null_forgiving_expression
    | object_creation_expression
    | delegate_creation_expression
    | anonymous_object_creation_expression
    | typeof_expression
    | sizeof_expression
    | checked_expression
    | unchecked_expression
    | default_value_expression
    | nameof_expression    
    | anonymous_method_expression
    | pointer_member_access     // unsafe code support
    | pointer_element_access    // unsafe code support
    | stackalloc_expression
    ;

Obs! Dessa grammatikregler är inte ANTLR-redo eftersom de ingår i en uppsättning ömsesidigt vänsterrekursiva regler (primary_expression, primary_no_array_creation_expression, member_access, invocation_expression, element_access, post_increment_expression, , post_decrement_expressionoch null_forgiving_expressionpointer_member_access pointer_element_access) som ANTLR inte hanterar. Standardtekniker kan användas för att transformera grammatiken för att ta bort den ömsesidiga vänsterrekursionen. Detta har inte gjorts eftersom inte alla parsningsstrategier kräver det (t.ex. en LALR-parser skulle inte) och att göra det skulle dölja strukturen och beskrivningen. slutkommentar

pointer_member_access (§23.6.3) och pointer_element_access (§23.6.4) är endast tillgängliga i osäker kod (§23).

Primära uttryck delas mellan array_creation_expressions och primary_no_array_creation_expressions. Genom att behandla array_creation_expression på det här sättet, i stället för att lista den tillsammans med de andra enkla uttrycksformulären, kan grammatiken inte tillåta potentiellt förvirrande kod som

object o = new int[3][1];

som annars skulle tolkas som

object o = (new int[3])[1];

12.8.2 Literaler

En primary_expression som består av en literal (§6.4.5) klassificeras som ett värde.

12.8.3 Interpolerade stränguttryck

En interpolated_string_expression består av $, $@eller @$, omedelbart följt av text inom " tecken. I den citerade texten finns det noll eller fler interpoleringar avgränsade med { och tecken, som var och } en omger ett uttryck och valfria formateringsspecifikationer.

Interpolerade stränguttryck har två formulär; regelbundna (interpolated_regular_string_expression) och ordagranna (interpolated_verbatim_string_expression); som lexikalt liknar, men skiljer sig semantiskt från, de två formerna av strängliteraler (§6.4.5.6).

interpolated_string_expression
    : interpolated_regular_string_expression
    | interpolated_verbatim_string_expression
    ;

// interpolated regular string expressions

interpolated_regular_string_expression
    : Interpolated_Regular_String_Start Interpolated_Regular_String_Mid?
      ('{' regular_interpolation '}' Interpolated_Regular_String_Mid?)*
      Interpolated_Regular_String_End
    ;

regular_interpolation
    : expression (',' interpolation_minimum_width)?
      Regular_Interpolation_Format?
    ;

interpolation_minimum_width
    : constant_expression
    ;

Interpolated_Regular_String_Start
    : '$"'
    ;

// the following three lexical rules are context sensitive, see details below

Interpolated_Regular_String_Mid
    : Interpolated_Regular_String_Element+
    ;

Regular_Interpolation_Format
    : ':' Interpolated_Regular_String_Element+
    ;

Interpolated_Regular_String_End
    : '"'
    ;

fragment Interpolated_Regular_String_Element
    : Interpolated_Regular_String_Character
    | Simple_Escape_Sequence
    | Hexadecimal_Escape_Sequence
    | Unicode_Escape_Sequence
    | Open_Brace_Escape_Sequence
    | Close_Brace_Escape_Sequence
    ;

fragment Interpolated_Regular_String_Character
    // Any character except " (U+0022), \\ (U+005C),
    // { (U+007B), } (U+007D), and New_Line_Character.
    : ~["\\{}\u000D\u000A\u0085\u2028\u2029]
    ;

// interpolated verbatim string expressions

interpolated_verbatim_string_expression
    : Interpolated_Verbatim_String_Start Interpolated_Verbatim_String_Mid?
      ('{' verbatim_interpolation '}' Interpolated_Verbatim_String_Mid?)*
      Interpolated_Verbatim_String_End
    ;

verbatim_interpolation
    : expression (',' interpolation_minimum_width)?
      Verbatim_Interpolation_Format?
    ;

Interpolated_Verbatim_String_Start
    : '$@"'
    | '@$"'
    ;

// the following three lexical rules are context sensitive, see details below

Interpolated_Verbatim_String_Mid
    : Interpolated_Verbatim_String_Element+
    ;

Verbatim_Interpolation_Format
    : ':' Interpolated_Verbatim_String_Element+
    ;

Interpolated_Verbatim_String_End
    : '"'
    ;

fragment Interpolated_Verbatim_String_Element
    : Interpolated_Verbatim_String_Character
    | Quote_Escape_Sequence
    | Open_Brace_Escape_Sequence
    | Close_Brace_Escape_Sequence
    ;

fragment Interpolated_Verbatim_String_Character
    : ~["{}]    // Any character except " (U+0022), { (U+007B) and } (U+007D)
    ;

// lexical fragments used by both regular and verbatim interpolated strings

fragment Open_Brace_Escape_Sequence
    : '{{'
    ;

fragment Close_Brace_Escape_Sequence
    : '}}'
    ;

Sex av de lexikala regler som definieras ovan är sammanhangskänsliga enligt följande:

Regel Kontextuella krav
Interpolated_Regular_String_Mid Identifieras endast efter en Interpolated_Regular_String_Start, mellan följande interpolationer och före motsvarande Interpolated_Regular_String_End.
Regular_Interpolation_Format Identifieras endast inom en regular_interpolation och när startkolonet (:) inte är kapslat inom någon typ av hakparentes (parenteser/klammerparenteser/kvadrat).
Interpolated_Regular_String_End Identifieras endast efter en Interpolated_Regular_String_Start och endast om några mellanliggande token antingen är Interpolated_Regular_String_Mids eller token som kan ingå i regular_interpolations, inklusive token för alla interpolated_regular_string_expressionsom ingår i sådana interpolationer.
Interpolated_Verbatim_String_Mid Verbatim_Interpolation_Format Interpolated_Verbatim_String_End Erkännandet av dessa tre regler följer motsvarande regler ovan med varje angiven vanlig grammatikregel ersatt av motsvarande ordagranna .

Obs! Ovanstående regler är kontextkänsliga eftersom deras definitioner överlappar andra tokens på språket. slutkommentar

Obs! Grammatiken ovan är inte ANTLR-klar på grund av kontextkänsliga lexikala regler. Precis som med andra lexergeneratorer stöder ANTLR kontextkänsliga lexikala regler, till exempel användning av dess lexikala lägen, men detta är en implementeringsdetalj och därför inte en del av denna specifikation. slutkommentar

En interpolated_string_expression klassificeras som ett värde. Om den omedelbart konverteras till System.IFormattable eller System.FormattableString med en implicit interpolerad strängkonvertering (§10.2.5) har det interpolerade stränguttrycket den typen. Annars har den typen string.

Obs! Skillnaderna mellan möjliga typer som en interpolated_string_expression kan fastställas i dokumentationen för System.String (§C.2) och System.FormattableString (§C.3). slutkommentar

Innebörden av en interpolering, både regular_interpolation och verbatim_interpolation, är att formatera värdet för uttrycket som ett string antingen enligt det format som anges av Regular_Interpolation_Format eller Verbatim_Interpolation_Format, eller enligt ett standardformat för typen av uttryck. Den formaterade strängen ändras sedan av interpolation_minimum_width, om någon, för att producera den slutgiltiga string som ska interpoleras till interpolated_string_expression.

Obs! Hur standardformatet för en typ fastställs beskrivs i dokumentationen för System.String (§C.2) och System.FormattableString (§C.3). Beskrivningar av standardformat, som är identiska för Regular_Interpolation_Format och Verbatim_Interpolation_Format, finns i dokumentationen för System.IFormattable (§C.4) och i andra typer i standardbiblioteket (§C). slutkommentar

I en interpolation_minimum_width ska constant_expression ha en implicit konvertering till int. Låt fältbredden vara det absoluta värdet för den här constant_expression och justeringen vara tecknet (positivt eller negativt) för värdet för den här constant_expression:

  • Om värdet för fältbredden är mindre än eller lika med längden på den formaterade strängen ändras inte den formaterade strängen.
  • Annars är den formaterade strängen vadderad med blankstegstecken så att dess längd är lika med fältbredden:
    • Om justeringen är positiv är den formaterade strängen högerjusterad genom att lägga till utfyllnaden i förväg.
    • Annars är den vänsterjusterad genom att lägga till utfyllnad.

Den övergripande innebörden av en interpolated_string_expression, inklusive ovanstående formatering och utfyllnad av interpoleringar, definieras genom en konvertering av uttrycket till en metodanrop: om typen av uttrycket är System.IFormattable eller System.FormattableString den metoden är System.Runtime.CompilerServices.FormattableStringFactory.Create (§C.3) som returnerar ett värde av typen System.FormattableString; annars ska typen vara string och metoden är string.Format (§C.2) som returnerar ett värde av typen string.

I båda fallen består argumentlistan för anropet av en formatsträngliteral med formatspecifikationer för varje interpolation och ett argument för varje uttryck som motsvarar formatspecifikationerna.

Formatsträngliteralen konstrueras på följande sätt, där N är antalet interpolationer i interpolated_string_expression. Formatsträngliteralen består av i ordning:

  • Tecknen i Interpolated_Regular_String_Start eller Interpolated_Verbatim_String_Start
  • Tecknen i Interpolated_Regular_String_Mid eller Interpolated_Verbatim_String_Mid, om några
  • N ≥ 1 Om sedan för varje tal I från 0 till N-1:
    • En platshållarspecifikation:
      • Ett vänster klammerparentestecken ({)
      • Decimalrepresentationen av I
      • Om motsvarande regular_interpolation eller verbatim_interpolation sedan har en interpolation_minimum_width, ett kommatecken (,) följt av decimalrepresentationen av värdet för constant_expression
      • Tecknen i Regular_Interpolation_Format eller Verbatim_Interpolation_Format av motsvarande regular_interpolation eller verbatim_interpolation
      • Ett höger klammerparentestecken (})
    • Tecknen i Interpolated_Regular_String_Mid eller Interpolated_Verbatim_String_Mid omedelbart efter motsvarande interpolation, om någon
  • Slutligen tecknen i Interpolated_Regular_String_End eller Interpolated_Verbatim_String_End.

De efterföljande argumenten är uttrycken från interpolationerna, om några, i ordning.

När en interpolated_string_expression innehåller flera interpolationer utvärderas uttrycken i dessa interpoleringar i textordning från vänster till höger.

Exempel:

I det här exemplet används följande formatspecifikationsfunktioner:

  • formatspecifikationen X som formaterar heltal som versaler hexadecimalt,
  • standardformatet för ett string värde är själva värdet,
  • positiva justeringsvärden som högerjusterar inom den angivna minsta fältbredden,
  • negativa justeringsvärden som vänsterjusterar inom den angivna minsta fältbredden.
  • definierade konstanter för interpolation_minimum_width och
  • }} och {{ formateras som { respektive}.

Given:

string text = "red";
int number = 14;
const int width = -4;

Sedan:

Interpolerat stränguttryck Motsvarande betydelse som string Värde
$"{text}" string.Format("{0}", text) "red"
$"{{text}}" string.Format("{{text}}) "{text}"
$"{ text , 4 }" string.Format("{0,4}", text) " red"
$"{ text , width }" string.Format("{0,-4}", text) "red "
$"{number:X}" string.Format("{0:X}", number) "E"
$"{text + '?'} {number % 3}" string.Format("{0} {1}", text + '?', number % 3) "red? 2"
$"{text + $"[{number}]"}" string.Format("{0}", text + string.Format("[{0}]", number)) "red[14]"
$"{(number==0?"Zero":"Non-zero")}" string.Format("{0}", (number==0?"Zero":"Non-zero")) "Non-zero"

slutexempel

12.8.4 Enkla namn

En simple_name består av en identifierare, eventuellt följt av en typargumentlista:

simple_name
    : identifier type_argument_list?
    ;

En simple_name är antingen i formuläret I eller i formuläret I<A₁, ..., Aₑ>, där I är en enda identifierare och I<A₁, ..., Aₑ> är en valfri type_argument_list. När ingen type_argument_list har angetts bör du överväga e att vara noll. Simple_name utvärderas och klassificeras enligt följande:

  • Om e är noll och simple_name visas inom ett lokalt variabeldeklarationsutrymme (§7.3) som direkt innehåller en lokal variabel, parameter eller konstant med namnet I, refererar simple_name till den lokala variabeln, parametern eller konstanten och klassificeras som en variabel eller ett värde.
  • Om e är noll och simple_name visas i en allmän metoddeklaration men utanför attributen för dess method_declaration, och om den deklarationen innehåller en typparameter med namnet I, refererar simple_name till den typparametern.
  • Annars för varje instanstyp T (§15.3.2), som börjar med instanstypen för den omedelbart omslutande typdeklarationen och fortsätter med instanstypen för varje omslutande klass eller structdeklaration (om någon):
    • Om e är noll och deklarationen av T innehåller en typparameter med namnet Irefererar simple_name till den typparametern.
    • Annars, om ett medlemsuppslag (§12.5) av I i T med e typargument skapar en matchning:
      • Om T är instanstypen för den omedelbart omslutande klassen eller structtypen och sökningen identifierar en eller flera metoder, är resultatet en metodgrupp med ett associerat instansuttryck av this. Om en typargumentlista angavs används den för att anropa en generisk metod (§12.8.10.2).
      • Annars, om T är instanstypen för den omedelbart omslutande klassen eller structtypen, om sökningen identifierar en instansmedlem och om referensen inträffar inom blocket för en instanskonstruktor, en instansmetod eller en instansåtkomstor (§12.2.1), blir resultatet detsamma som en medlemsåtkomst (§12.8.7) av formuläret this.I. Detta kan bara inträffa när e är noll.
      • Annars är resultatet detsamma som en medlemsåtkomst (§12.8.7) av formuläret T.I eller T.I<A₁, ..., Aₑ>.
  • I annat fall utvärderas följande steg tills en entitet finns för varje namnområde Nsom börjar med namnområdet där simple_name inträffar, fortsätter med varje omslutande namnområde (om det finns) och slutar med det globala namnområdet:
    • Om e är noll och I är namnet på ett namnområde i N, så:
      • Om platsen där simple_name inträffar omges av en namnområdesdeklaration för N och namnområdesdeklarationen innehåller en extern_alias_directive eller using_alias_directive som associerar namnet I med ett namnområde eller en typ, är simple_name tvetydig och ett kompileringsfel inträffar.
      • Annars refererar simple_name till namnområdet med namnet I i N.
    • Annars, om N innehåller en tillgänglig typ med namn I och e typparametrar, så:
      • Om e är noll och platsen där simple_name inträffar omges av en namnområdesdeklaration för N och namnområdesdeklarationen innehåller en extern_alias_directive eller using_alias_directive som associerar namnet I med ett namnområde eller typ, är simple_name tvetydig och ett kompileringsfel inträffar.
      • Annars refererar namespace_or_type_name till den typ som skapats med de angivna typargumenten.
    • Annars om platsen där simple_name inträffar omges av en namnområdesdeklaration för N:
      • Om e är noll och namnområdesdeklarationen innehåller en extern_alias_directive eller using_alias_directive som associerar namnet I med ett importerat namnområde eller typ refererar simple_name till namnområdet eller typen.
      • Annars, om namnrymderna som importeras av using_namespace_directivei namnområdesdeklarationen innehåller exakt en typ med namn I och e typparametrar, refererar simple_name till den typen som skapats med de angivna typargumenten.
      • Om namnrymderna som importerats av using_namespace_directivei namnområdesdeklarationen innehåller fler än en typ med namn I - och e typparametrar , är simple_name tvetydigt och ett kompileringsfel inträffar.

    Obs! Hela det här steget är exakt parallellt med motsvarande steg i bearbetningen av en namespace_or_type_name (§7.8). slutkommentar

  • Annars, om e är noll och I är identifieraren _, är simple_name en enkel ignorera, vilket är en form av deklarationsuttryck (§12.17).
  • Annars är simple_name odefinierat och ett kompileringsfel inträffar.

12.8.5 Parentesiserade uttryck

En parenthesized_expression består av ett uttryck som omges av parenteser.

parenthesized_expression
    : '(' expression ')'
    ;

En parenthesized_expression utvärderas genom att uttrycket utvärderas inom parenteserna. Om uttrycket inom parenteserna anger ett namnområde eller en typ uppstår ett kompileringsfel. Annars är resultatet av parenthesized_expression resultatet av utvärderingen av det inneslutna uttrycket.

12.8.6 Tupplar

En tuple_expression representerar en tupplar och består av två eller flera kommaavgränsade och valfritt namngivna uttrycksom omges av parenteser. En deconstruction_expression är en kortfattad syntax för en tupplar som innehåller implicit inskrivna deklarationsuttryck.

tuple_expression
    : '(' tuple_element (',' tuple_element)+ ')'
    | deconstruction_expression
    ;
    
tuple_element
    : (identifier ':')? expression
    ;
    
deconstruction_expression
    : 'var' deconstruction_tuple
    ;
    
deconstruction_tuple
    : '(' deconstruction_element (',' deconstruction_element)+ ')'
    ;

deconstruction_element
    : deconstruction_tuple
    | identifier
    ;

En tuple_expression klassificeras som en tuppl.

En deconstruction_expression var (e1, ..., en) är en förkortning för tuple_expression (var e1, ..., var en) och följer samma beteende. Detta gäller rekursivt för alla kapslade deconstruction_tuplei deconstruction_expression. Varje identifierare kapslad inom en deconstruction_expression introducerar därmed ett deklarationsuttryck (§12.17). Därför kan en deconstruction_expression bara ske till vänster i en enkel tilldelning.

Ett tupplaruttryck har en typ om och endast om vart och ett av dess elementuttryck Ei har en typ Ti. Typen ska vara en tuppelns typ av samma aritet som tuppelns uttryck, där varje element anges av följande:

  • Om tuppelns element i motsvarande position har ett namn Niska tuppelns typelement vara Ti Ni.
  • Annars, om Ei är av formuläret Ni eller E.Ni eller E?.Ni då tuppeln typelementet skall vara Ti Ni, såvida inte något av följande gäller:
    • Ett annat element i tuppelns uttryck har namnet Ni, eller
    • Ett annat tupppelelement utan namn har ett tupppelelementuttryck av formuläret Ni eller E.Ni eller E?.Ni, eller
    • Ni är av formatet ItemX, där X är en sekvens med icke-initierade0 decimalsiffror som kan representera positionen för ett tupplar och X som inte representerar elementets position.
  • I annat fall ska tuppelns typelement vara Ti.

Ett tupplaruttryck utvärderas genom att utvärdera vart och ett av dess elementuttryck i ordning från vänster till höger.

Ett tuppelns värde kan hämtas från ett tupppeluttryck genom att konvertera det till en tuppelns typ (§10.2.13), genom att omklassificera det som ett värde (§12.2.2)) eller genom att göra det till mål för en dekonstruktionstilldelning (§12.21.2).

Exempel:

(int i, string) t1 = (i: 1, "One");
(long l, string) t2 = (l: 2, null);
var t3 = (i: 3, "Three");          // (int i, string)
var t4 = (i: 4, null);             // Error: no type

I det här exemplet är alla fyra tuppelns uttryck giltiga. De första två, t1 och t2, använder inte typen av tuppelns uttryck, utan tillämpar i stället en implicit tuppelns konvertering. När det gäller t2, förlitar sig den implicita tuppelns konvertering på implicita konverteringar från 2 till long och från null till string. Det tredje tuppelns uttryck har en typ (int i, string)och kan därför omklassificeras som ett värde av den typen. Deklarationen av t4, å andra sidan, är ett fel: Tuppelns uttryck har ingen typ eftersom dess andra element inte har någon typ.

if ((x, y).Equals((1, 2))) { ... };

Det här exemplet visar att tupplar ibland kan leda till flera lager parenteser, särskilt när tuppelns uttryck är det enda argumentet till en metodanrop.

slutexempel

12.8.7 Medlemsåtkomst

12.8.7.1 Allmänt

En member_access består av en primary_expression, en predefined_type eller en qualified_alias_member följt av en "." token följt av en identifierare, eventuellt följt av en type_argument_list.

member_access
    : primary_expression '.' identifier type_argument_list?
    | predefined_type '.' identifier type_argument_list?
    | qualified_alias_member '.' identifier type_argument_list?
    ;

predefined_type
    : 'bool' | 'byte' | 'char' | 'decimal' | 'double' | 'float' | 'int'
    | 'long' | 'object' | 'sbyte' | 'short' | 'string' | 'uint' | 'ulong'
    | 'ushort'
    ;

Den qualified_alias_member produktionen definieras i §14.8.

En member_access är antingen i formuläret E.I eller i formuläret E.I<A₁, ..., Aₑ>, där E är en primary_expression, predefined_type eller qualified_alias_member, I är en enda identifierare och <A₁, ..., Aₑ> är en valfri type_argument_list. När ingen type_argument_list har angetts bör du överväga e att vara noll.

En member_access med en primary_expression av typen dynamic är dynamiskt bunden (§12.3.3). I det här fallet klassificerar kompilatorn medlemsåtkomsten som en egenskapsåtkomst av typen dynamic. Reglerna nedan för att fastställa innebörden av member_access tillämpas sedan vid körning, med hjälp av körningstypen i stället för kompileringstidstypen för primary_expression. Om denna körningsklassificering leder till en metodgrupp ska medlemsåtkomsten vara primary_expression för en invocation_expression.

Member_access utvärderas och klassificeras på följande sätt:

  • Om e är noll och E är ett namnområde och E innehåller ett kapslat namnområde med namnet Iblir resultatet det namnområdet.
  • Annars, om E är ett namnområde och E innehåller en tillgänglig typ med namn I - och K typparametrar, är resultatet den typen som konstruerats med de angivna typargumenten.
  • Om E klassificeras som en typ, om E inte är en typparameter, och om en medlemssökning (§12.5) i I E med K typparametrar genererar en matchning, E.I utvärderas och klassificeras så här:

    Obs! När resultatet av en sådan medlemssökning är en metodgrupp och K är noll, kan metodgruppen innehålla metoder som har typparametrar. Detta gör att sådana metoder kan övervägas för typarguments slutsatsdragning. slutkommentar

    • Om I identifierar en typ är resultatet den typen som konstruerats med alla angivna typargument.
    • Om I identifierar en eller flera metoder blir resultatet en metodgrupp utan associerat instansuttryck.
    • Om I identifierar en statisk egenskap blir resultatet en egenskapsåtkomst utan associerat instansuttryck.
    • Om I identifierar ett statiskt fält:
      • Om fältet är skrivskyddat och referensen inträffar utanför den statiska konstruktorn för klassen eller structen där fältet deklareras, är resultatet ett värde, nämligen värdet för det statiska fältet I i E.
      • Annars är resultatet en variabel, nämligen det statiska fältet I i E.
    • Om I identifierar en statisk händelse:
      • Om referensen inträffar inom klassen eller structen där händelsen deklareras och händelsen deklarerades utan event_accessor_declarations (§15.8.1), bearbetas den E.I exakt som om I det var ett statiskt fält.
      • Annars är resultatet en händelseåtkomst utan associerat instansuttryck.
    • Om I identifierar en konstant är resultatet ett värde, nämligen värdet för den konstanten.
    • Om I identifierar en uppräkningsmedlem är resultatet ett värde, nämligen värdet för den uppräkningsmedlemmen.
    • Annars E.I är en ogiltig medlemsreferens och ett kompileringsfel inträffar.
  • Om E är en egenskapsåtkomst, indexerareåtkomst, variabel eller värde, vars typ är T, och en medlemssökning (§12.5) av I med T K typargument skapar en matchning, utvärderas och klassificeras så E.I här:
    • E Om är en egenskaps- eller indexerareåtkomst erhålls först värdet för egenskapen eller indexerarens åtkomst (§12.2.2) och E omklassificeras som ett värde.
    • Om I identifierar en eller flera metoder är resultatet en metodgrupp med ett associerat instansuttryck av E.
    • Om I identifierar en instansegenskap blir resultatet en egenskapsåtkomst med ett associerat instansuttryck av E och en associerad typ som är egenskapens typ. Om T är en klasstyp väljs den associerade typen från den första deklarationen eller åsidosättningen av egenskapen som hittades när du började med Toch söker igenom dess basklasser.
    • Om T är en class_type och I identifierar ett instansfält för den class_type:
      • Om värdet E för är nullgenereras en System.NullReferenceException .
      • Annars, om fältet är skrivskyddat och referensen inträffar utanför en instanskonstruktor för klassen där fältet deklareras, är resultatet ett värde, nämligen värdet för fältet I i objektet som refereras av E.
      • Annars är resultatet en variabel, nämligen fältet I i objektet som refereras av E.
    • Om T är en struct_type och I identifierar ett instansfält för den struct_type:
      • Om E är ett värde, eller om fältet är skrivskyddat och referensen inträffar utanför en instanskonstruktor för den struct där fältet deklareras, blir resultatet ett värde, nämligen värdet för fältet I i den struct-instans som anges av E.
      • Annars är resultatet en variabel, nämligen fältet I i den struct-instans som ges av E.
    • Om I identifierar en instanshändelse:
  • I annat fall görs ett försök att bearbeta E.I som ett tilläggsmetodanrop (§12.8.10.3). Om detta misslyckas E.I är en ogiltig medlemsreferens och ett bindningstidsfel inträffar.

12.8.7.2 Identiska enkla namn och typnamn

I en medlemsåtkomst av formuläret E.I, om E är en enskild identifierare, och om innebörden av E som en simple_name (§12.8.4) är en konstant, fält, egenskap, lokal variabel eller parameter med samma typ som innebörden av E som en type_name (§7.8.1), tillåts båda möjliga betydelser av E . Medlemssökningen av E.I är aldrig tvetydig, eftersom I den nödvändigtvis ska vara medlem av typen E i båda fallen. Med andra ord tillåter regeln bara åtkomst till de statiska medlemmarna och kapslade typer av E var ett kompileringsfel annars skulle ha inträffat.

Exempel:

struct Color
{
    public static readonly Color White = new Color(...);
    public static readonly Color Black = new Color(...);
    public Color Complement() => new Color(...);
}

class A
{
    public «Color» Color;              // Field Color of type Color

    void F()
    {
        Color = «Color».Black;         // Refers to Color.Black static member
        Color = Color.Complement();  // Invokes Complement() on Color field
    }

    static void G()
    {
        «Color» c = «Color».White;       // Refers to Color.White static member
    }
}

I expository-syfte avgränsas endast A de förekomster av identifieraren Color som refererar till Color typen av i klassen av «...», och de som refererar till Color fältet är inte det.

slutexempel

12.8.8 Null Villkorlig medlemsåtkomst

En null_conditional_member_access är en villkorsstyrd version av member_access (§12.8.7) och det är ett bindningstidsfel om resultattypen är void. För ett villkorligt null-uttryck där resultattypen kan visas void (§12.8.11).

En null_conditional_member_access består av en primary_expression följt av de två tokensna "?" och ".", följt av en identifierare med en valfri type_argument_list, följt av noll eller fler dependent_accesses som kan föregripas av en null_forgiving_operator.

null_conditional_member_access
    : primary_expression '?' '.' identifier type_argument_list?
      (null_forgiving_operator? dependent_access)*
    ;
    
dependent_access
    : '.' identifier type_argument_list?    // member access
    | '[' argument_list ']'                 // element access
    | '(' argument_list? ')'                // invocation
    ;

null_conditional_projection_initializer
    : primary_expression '?' '.' identifier type_argument_list?
    ;

Ett null_conditional_member_access uttryck E är av formuläret P?.A. Innebörden av E bestäms på följande sätt:

  • Om typen av P är en nullbar värdetyp:

    Låt vara T typen av P.Value.A.

    • Om T är en typparameter som inte är känd för att vara antingen en referenstyp eller en värdetyp som inte kan nulliseras uppstår ett kompileringsfel.

    • Om T är en icke-nullbar värdetyp är T?typen , E och innebörden av E är densamma som innebörden av:

      ((object)P == null) ? (T?)null : P.Value.A
      

      Förutom att P utvärderas bara en gång.

    • Annars är Ttypen av E , och innebörden av E är densamma som innebörden av:

      ((object)P == null) ? (T)null : P.Value.A
      

      Förutom att P utvärderas bara en gång.

  • Annars:

    Låt vara T typen av uttrycket P.A.

    • Om T är en typparameter som inte är känd för att vara antingen en referenstyp eller en värdetyp som inte kan nulliseras uppstår ett kompileringsfel.

    • Om T är en icke-nullbar värdetyp är T?typen , E och innebörden av E är densamma som innebörden av:

      ((object)P == null) ? (T?)null : P.A
      

      Förutom att P utvärderas bara en gång.

    • Annars är Ttypen av E , och innebörden av E är densamma som innebörden av:

      ((object)P == null) ? (T)null : P.A
      

      Förutom att P utvärderas bara en gång.

Obs! I ett uttryck i formuläret:

P?.A₀?.A₁

sedan om P utvärderas till null varken A₀ eller A₁ utvärderas. Detsamma gäller om ett uttryck är en sekvens av null_conditional_member_access eller null_conditional_element_access §12.8.13-åtgärder .

slutkommentar

En null_conditional_projection_initializer är en begränsning av null_conditional_member_access och har samma semantik. Den inträffar endast som en projektionsinitierare i ett anonymt objektskapandeuttryck (§12.8.17.7).

12.8.9 Null-förlåtande uttryck

12.8.9.1 Allmänt

Ett null-förlåtande uttrycks värde, typ, klassificering (§12.2) och safe-context (§16.4.12) är värdet, typen, klassificeringen och säkerhetskontexten för dess primary_expression.

null_forgiving_expression
    : primary_expression null_forgiving_operator
    ;

null_forgiving_operator
    : '!'
    ;

Obs! Operatorerna för postfixet null-forgiving och prefix logisk negation (§12.9.4), som representeras av samma lexikala token (!), är distinkta. Endast den senare får åsidosättas (§15.10), definitionen av den null-förlåtande operatorn är fast. slutkommentar

Det är ett kompileringsfel att tillämpa operatorn null-forgiving mer än en gång på samma uttryck, trots mellanliggande parenteser.

Exempel: Följande är alla ogiltiga:

var p = q!!;            // error: applying null_forgiving_operator more than once
var s = ( ( m(t) ! ) )! // error: null_forgiving_operator applied twice to m(t)

slutexempel

Resten av den här underklienten och följande underklienter för syskon är villkorligt normativa.

En kompilator som utför statisk nulltillståndsanalys (§8.9.5) måste följa följande specifikation.

Operatorn null-forgiving är en pseudoåtgärd för kompileringstid som används för att informera en kompilators statiska nulltillståndsanalys. Den har två användningsområden: för att åsidosätta en kompilators bestämning att ett uttryck kanske är null och för att åsidosätta en kompilator som utfärdar en varning om nullbarhet.

Att tillämpa operatorn null-forgiving på ett uttryck för vilket en kompilators statiska nulltillståndsanalys inte ger några varningar är inte ett fel.

12.8.9.2 Åsidosätta en "kanske null"-bestämning

Under vissa omständigheter kan en kompilators statiska nulltillståndsanalys avgöra att ett uttryck har null-tillståndet kanske null och utfärda en diagnostikvarning när annan information anger att uttrycket inte kan vara null. Om operatorn null-forgiving tillämpas på ett sådant uttryck informeras kompilatorns statiska nulltillståndsanalys om att null-tillståndet inte är null, vilket både förhindrar diagnostikvarningen och kan informera om pågående analys.

Exempel: Tänk på följande:

#nullable enable
public static void M()
{
    Person? p = Find("John");                  // returns Person?
    if (IsValid(p))
    {
       Console.WriteLine($"Found {p!.Name}");  // p can't be null
    }
}

public static bool IsValid(Person? person) =>
    person != null && person.Name != null;

Om IsValid returnerar truekan , p på ett säkert sätt derefereras för att komma åt dess Name egenskap, och varningen "dereferencing av ett eventuellt null-värde" kan ignoreras med hjälp av !.

slutexempel

Exempel: Operatorn null-forgiving bör användas med försiktighet. Tänk på följande:

#nullable enable
int B(int? x)
{
    int y = (int)x!; // quash warning, throw at runtime if x is null
    return y;
}

Här tillämpas operatorn null-forgiving på en värdetyp och quashes alla varningar på x. Men om x är null vid körning genereras ett undantag som null inte kan gjutas till int.

slutexempel

12.8.9.3 Åsidosätta andra null-analysvarningar

Förutom att åsidosätta kanske null-bestämningar som ovan kan det finnas andra omständigheter där det är önskvärt att åsidosätta en kompilators statiska null-tillståndsanalys som bestämning av att ett uttryck kräver en eller flera varningar. Tillämpa operatorn null-forgiving på sådana uttrycksbegäranden att kompilatorn inte utfärdar några varningar för uttrycket. Som svar kan en kompilator välja att inte utfärda varningar och kan också ändra sin ytterligare analys.

Exempel: Tänk på följande:

#nullable enable
public static void Assign(out string? lv, string? rv) { lv = rv; }

public string M(string? t)
{
    string s;
    Assign(out s!, t ?? "«argument was null»");
    return s;
}

Typerna av metodens Assignparametrar, lv & rv, är string?, med lv en utdataparameter, och den utför en enkel tilldelning.

Metoden M skickar variabeln s, av typen string, som Assignutdataparameter. Kompilatorn använde en varning eftersom s den inte är en nullbar variabel. AssignEftersom det andra argumentet inte kan vara null används operatorn null-forgiving för att stoppa varningen.

slutexempel

Slutet på villkorsstyrd normativ text.

12.8.10 Anropsuttryck

12.8.10.1 Allmänt

En invocation_expression används för att anropa en metod.

invocation_expression
    : primary_expression '(' argument_list? ')'
    ;

Primary_expression kan vara en null_forgiving_expression om och bara om den har en delegate_type.

En invocation_expression är dynamiskt bunden (§12.3.3) om minst något av följande gäller:

  • Primary_expression har kompileringstidstypen dynamic.
  • Minst ett argument av den valfria argument_list har kompileringstidstypen dynamic.

I det här fallet klassificerar kompilatorn invocation_expression som ett värde av typen dynamic. Reglerna nedan för att fastställa innebörden av invocation_expression tillämpas sedan vid körning, med körningstypen i stället för kompileringstidstypen för de primary_expression och argument som har kompileringstidstypen dynamic. Om primary_expression inte har kompileringstidstyp dynamicgenomgår metodanropet en begränsad kompileringstidskontroll enligt beskrivningen i §12.6.5.

En primary_expression för en invocation_expression ska vara en metodgrupp eller ett värde för en delegate_type. Om primary_expression är en metodgrupp är invocation_expression ett metodanrop (§12.8.10.2). Om primary_expression är ett värde för en delegate_type är invocation_expression ett ombudsanrop (§12.8.10.4). Om primary_expression varken är en metodgrupp eller ett värde för en delegate_type uppstår ett bindningstidsfel.

Den valfria argument_list (§12.6.2) innehåller värden eller variabelreferenser för metodens parametrar.

Resultatet av utvärderingen av en invocation_expression klassificeras på följande sätt:

  • Om invocation_expression anropar metoden returns-no-value (§15.6.1) eller ett ombud för return-no-value är resultatet ingenting. Ett uttryck som klassificeras som ingenting tillåts endast i samband med en statement_expression (§13.7) eller som brödtexten i en lambda_expression (§12.19). Annars uppstår ett bindningstidsfel.
  • Om invocation_expression anropar en return-by-ref-metod (§15.6.1) eller ett ombud för return-by-ref är resultatet en variabel med en associerad typ av returtyp för metoden eller ombudet. Om anropet är av en instansmetod och mottagaren är av klasstyp T, väljs den associerade typen från den första deklarationen eller åsidosätter metoden som hittades när den började med T och söker igenom dess basklasser.
  • I annat fall anropar invocation_expression en metod för return-by-value (§15.6.1) eller ombud för retur per värde, och resultatet är ett värde med en associerad typ av returtyp för metoden eller ombudet. Om anropet är av en instansmetod och mottagaren är av klasstyp T, väljs den associerade typen från den första deklarationen eller åsidosätter metoden som hittades när den började med T och söker igenom dess basklasser.

12.8.10.2 Metodanrop

För ett metodanrop ska primary_expression av invocation_expression vara en metodgrupp. Metodgruppen identifierar den enda metod som ska anropas eller uppsättningen överlagrade metoder som du kan välja en specifik metod att anropa från. I det senare fallet baseras fastställandet av den specifika metod som ska anropas på kontexten som tillhandahålls av argumenttyperna i argument_list.

Bindningstidsbearbetningen av ett metodanrop av formuläret M(A), där M är en metodgrupp (eventuellt inklusive en type_argument_list) och A är en valfri argument_list består av följande steg:

  • Uppsättningen kandidatmetoder för metodanropet konstrueras. För varje metod F som är associerad med metodgruppen M:
    • Om F är icke-generiskt, F är en kandidat när:
      • M har ingen typargumentlista, och
      • F gäller för A (§12.6.4.2).
    • Om F är generisk och M inte har någon typargumentlista, F är en kandidat när:
      • Typinferens (§12.6.3) lyckas och härleder en lista med typargument för anropet och
      • När de härledda typargumenten har ersatts med motsvarande metodtypsparametrar, uppfyller alla konstruerade typer i parameterlistan F sina begränsningar (§8.4.5), och parameterlistan för F är tillämplig för A (§12.6.4.2)
    • Om F är generisk och M innehåller en typargumentlista, F är en kandidat när:
      • F har samma antal metodtypsparametrar som angavs i typargumentlistan och
      • När typargumenten har ersatts med motsvarande metodtypsparametrar uppfyller alla konstruerade typer i parameterlistan F sina begränsningar (§8.4.5), och parameterlistan för F är tillämplig på A (§12.6.4.2).
  • Uppsättningen kandidatmetoder reduceras till att endast innehålla metoder från de mest härledda typerna: För varje metod C.F i uppsättningen, där C är den typ där metoden F deklareras, tas alla metoder som deklareras i en bastyp bort C från uppsättningen. Om C är en annan klasstyp än objecttas dessutom alla metoder som deklarerats i en gränssnittstyp bort från uppsättningen.

    Obs! Den här senare regeln har bara en effekt när metodgruppen var resultatet av ett medlemsuppslag på en typparameter som har en annan effektiv basklass än object och en icke-tom effektiv gränssnittsuppsättning. slutkommentar

  • Om den resulterande uppsättningen kandidatmetoder är tom avbryts ytterligare bearbetning längs följande steg, och i stället görs ett försök att bearbeta anropet som ett tilläggsmetodanrop (§12.8.10.3). Om detta misslyckas finns det inga tillämpliga metoder och ett bindningstidsfel inträffar.
  • Den bästa metoden för uppsättningen kandidatmetoder identifieras med hjälp av reglerna för överbelastningsmatchning i §12.6.4. Om det inte går att identifiera en enda bästa metod är metodanropet tvetydigt och ett bindningstidsfel inträffar. När du utför överbelastningsmatchning beaktas parametrarna för en generisk metod efter att ha ersatt typargumenten (tillhandahålls eller härleds) för motsvarande metodtypparametrar.

När en metod har valts och verifierats vid bindningstid enligt ovanstående steg bearbetas det faktiska körningsanropet enligt reglerna för funktionsmedlemsanrop som beskrivs i §12.6.6.

Obs! Den intuitiva effekten av de lösningsregler som beskrivs ovan är följande: Om du vill hitta den metod som anropas av ett metodanrop börjar du med den typ som anges av metodens anrop och fortsätter upp i arvskedjan tills minst en tillämplig, tillgänglig metoddeklaration som inte åsidosätter hittas. Utför sedan typinferens och överbelastningsmatchning på uppsättningen med tillämpliga, tillgängliga, icke-åsidosättningsmetoder som deklarerats i den typen och anropar den metod som valts. Om ingen metod hittades kan du i stället försöka bearbeta anropet som ett tilläggsmetodanrop. slutkommentar

12.8.10.3 Tilläggsmetodanrop

I ett metodanrop (§12.6.6.2) av ett av formulären

«expr» . «identifier» ( )  
«expr» . «identifier» ( «args» )  
«expr» . «identifier» < «typeargs» > ( )  
«expr» . «identifier» < «typeargs» > ( «args» )

Om den normala bearbetningen av anropet inte hittar några tillämpliga metoder görs ett försök att bearbeta konstruktionen som en tilläggsmetodanrop. Om «expr» eller någon av «args» har kompileringstidstyp dynamic, tillämpas inte tilläggsmetoder.

Målet är att hitta den bästa type_nameC, så att motsvarande statiska metodanrop kan ske:

C . «identifier» ( «expr» )  
C . «identifier» ( «expr» , «args» )  
C . «identifier» < «typeargs» > ( «expr» )  
C . «identifier» < «typeargs» > ( «expr» , «args» )

En tilläggsmetod Cᵢ.Mₑ är berättigad om:

  • Cᵢ är en icke-generisk klass som inte är kapslad
  • Namnet på är identifieraren Mₑ
  • Mₑ är tillgänglig och tillämplig när den tillämpas på argumenten som en statisk metod enligt ovan
  • Det finns en implicit identitet, referens eller boxningskonvertering från uttr till typen av den första parametern för Mₑ.

Sökningen efter C fortsätter enligt följande:

  • Från och med den närmaste omslutande namnområdesdeklarationen, fortsätter med varje omslutande namnområdesdeklaration och slutar med den innehållande kompileringsenheten, görs efterföljande försök att hitta en kandidatuppsättning med tilläggsmetoder:
    • Om det angivna namnområdet eller kompileringsenheten direkt innehåller icke-generiska typdeklarationer Cᵢ med berättigade tilläggsmetoder Mₑär uppsättningen med dessa tilläggsmetoder kandidatuppsättningen.
    • Om namnrymder som importeras med hjälp av namnområdesdirektiv i det angivna namnområdet eller kompileringsenheten direkt innehåller icke-generiska typdeklarationer Cᵢ med berättigade tilläggsmetoder Mₑ, är uppsättningen med dessa tilläggsmetoder kandidatuppsättningen.
  • Om ingen kandidatuppsättning hittas i någon omslutande namnområdesdeklaration eller kompileringsenhet uppstår ett kompileringsfel.
  • Annars tillämpas överbelastningsupplösning på kandidatuppsättningen enligt beskrivningen i §12.6.4. Om ingen bästa metod hittas uppstår ett kompileringsfel.
  • C är den typ inom vilken den bästa metoden deklareras som en tilläggsmetod.

Med hjälp av C som mål bearbetas metodanropet sedan som en statisk metodanrop (§12.6.6).

Obs! Till skillnad från en instansmetodanrop utlöses inget undantag när expr utvärderas till en null-referens. I stället skickas det här null värdet till tilläggsmetoden som det skulle vara via en vanlig statisk metodanrop. Det är upp till implementeringen av tilläggsmetoden att bestämma hur ett sådant anrop ska besvaras. slutkommentar

Ovanstående regler innebär att instansmetoder har företräde framför tilläggsmetoder, att tilläggsmetoder som är tillgängliga i deklarationer för inre namnområden har företräde framför tilläggsmetoder som är tillgängliga i deklarationer för yttre namnområden och att tilläggsmetoder som deklareras direkt i ett namnområde har företräde framför tilläggsmetoder som importeras till samma namnområde med ett med hjälp av ett namnområdesdirektiv.

Exempel:

public static class E
{
    public static void F(this object obj, int i) { }
    public static void F(this object obj, string s) { }
}

class A { }

class B
{
    public void F(int i) { }
}

class C
{
    public void F(object obj) { }
}

class X
{
    static void Test(A a, B b, C c)
    {
        a.F(1);            // E.F(object, int)
        a.F("hello");      // E.F(object, string)
        b.F(1);            // B.F(int)
        b.F("hello");      // E.F(object, string)
        c.F(1);            // C.F(object)
        c.F("hello");      // C.F(object)
    }
}

I exemplet Bhar metoden 's företräde framför den första tilläggsmetoden, och Cmetoden 's har företräde framför båda tilläggsmetoderna.

public static class C
{
    public static void F(this int i) => Console.WriteLine($"C.F({i})");
    public static void G(this int i) => Console.WriteLine($"C.G({i})");
    public static void H(this int i) => Console.WriteLine($"C.H({i})");
}

namespace N1
{
    public static class D
    {
        public static void F(this int i) => Console.WriteLine($"D.F({i})");
        public static void G(this int i) => Console.WriteLine($"D.G({i})");
    }
}

namespace N2
{
    using N1;

    public static class E
    {
        public static void F(this int i) => Console.WriteLine($"E.F({i})");
    }

    class Test
    {
        static void Main(string[] args)
        {
            1.F();
            2.G();
            3.H();
        }
    }
}

Utdata från det här exemplet är:

E.F(1)
D.G(2)
C.H(3)

D.G tar precendece över C.G, och E.F har företräde framför både D.F och C.F.

slutexempel

12.8.10.4 Delegera anrop

För ett ombudsanrop ska primary_expression för invocation_expression vara ett värde för en delegate_type. Med tanke på att delegate_type vara funktionsmedlem med samma parameterlista som delegate_type, ska delegate_type vara tillämpligt (§12.6.4.2) med avseende på argument_list av invocation_expression.

Körningsbearbetningen av ett ombudsanrop av formuläret D(A), där D är en primary_expression av en delegate_type och A är en valfri argument_list, består av följande steg:

  • D utvärderas. Om den här utvärderingen orsakar ett undantag körs inga ytterligare steg.
  • Argumentlistan A utvärderas. Om den här utvärderingen orsakar ett undantag körs inga ytterligare steg.
  • Värdet D för är markerat för att vara giltigt. Om värdet D för är nullgenereras en System.NullReferenceException och inga ytterligare steg körs.
  • Annars D är en referens till en ombudsinstans. Funktionsmedlemsanrop (§12.6.6) utförs på var och en av de anropsbara entiteterna i ombudets anropslista. För anropsbara entiteter som består av en instans- och instansmetod är instansen för anrop den instans som finns i den anropsbara entiteten.

Mer information om flera anropslistor utan parametrar finns i §20.6 .

12.8.11 Null villkorsstyrd anropsuttryck

En null_conditional_invocation_expression är syntaktiskt antingen en null_conditional_member_access (§12.8.8) eller null_conditional_element_access (§12.8.13) där den sista dependent_access är ett anropsuttryck (§12.8.10).

En null_conditional_invocation_expression sker inom ramen för en statement_expression (§13.7), anonymous_function_body (§12.19.1) eller method_body (§15.6.1).

Till skillnad från den syntaktiskt likvärdiga null_conditional_member_access eller null_conditional_element_access kan en null_conditional_invocation_expression klassificeras som ingenting.

null_conditional_invocation_expression
    : null_conditional_member_access null_forgiving_operator? '(' argument_list? ')'
    | null_conditional_element_access null_forgiving_operator? '(' argument_list? ')'
    ;

Den valfria null_forgiving_operator kan inkluderas om och endast om null_conditional_member_access eller null_conditional_element_access har en delegate_type.

Ett null_conditional_invocation_expression uttryck E är av formuläret P?A, där A är resten av den syntaktiskt likvärdiga null_conditional_member_access eller null_conditional_element_access, A börjar därför med . eller [. Låt PA oss beteckna sammanfogningen av P och A.

När E inträffar som en statement_expression är innebörden av E samma som innebörden av -instruktionen:

if ((object)P != null) PA

förutom att P utvärderas bara en gång.

När E inträffar som en anonymous_function_body eller method_body beror innebörden av E dess klassificering:

  • Om E klassificeras som ingenting är dess betydelse densamma som innebörden av blocket:

    { if ((object)P != null) PA; }
    

    förutom att P utvärderas bara en gång.

  • Annars är innebörden av E samma som innebörden av blocket:

    { return E; }
    

    och i sin tur beror innebörden av detta block på om E är syntaktiskt likvärdig med en null_conditional_member_access (§12.8.8) eller null_conditional_element_access (§12.8.13).

12.8.12 Elementåtkomst

12.8.12.1 Allmänt

En element_access består av en primary_no_array_creation_expression följt av en "[" token följt av en argument_list följt av en "]" token. Argument_list består av ett eller flera argument, avgränsade med kommatecken.

element_access
    : primary_no_array_creation_expression '[' argument_list ']'
    ;

Argument_list för en element_access får inte innehålla out eller ref argument.

En element_access är dynamiskt bunden (§12.3.3) om minst något av följande gäller:

  • Primary_no_array_creation_expression har kompileringstidstypen dynamic.
  • Minst ett uttryck för argument_list har kompileringstidstyp dynamic och primary_no_array_creation_expression inte har någon matristyp.

I det här fallet klassificerar kompilatorn element_access som ett värde av typen dynamic. Reglerna nedan för att fastställa innebörden av element_access tillämpas sedan vid körning, med hjälp av körningstypen i stället för kompileringstidstypen för de primary_no_array_creation_expression och argument_list uttryck som har kompileringstidstypen dynamic. Om primary_no_array_creation_expression inte har kompileringstidstyp dynamicgenomgår elementåtkomsten en begränsad kompileringstidskontroll enligt beskrivningen i §12.6.5.

Om primary_no_array_creation_expression för en element_access är ett värde för en array_type är element_access en matrisåtkomst (§12.8.12.2). Annars ska primary_no_array_creation_expression vara en variabel eller ett värde för en klass, struct eller gränssnittstyp som har en eller flera indexerare medlemmar, i vilket fall element_access är en indexerare åtkomst (§12.8.12.3).

12.8.12.2 Matrisåtkomst

För en matrisåtkomst ska primary_no_array_creation_expression för element_access vara ett värde för en array_type. Dessutom tillåts inte argument_list för en matrisåtkomst att innehålla namngivna argument. Antalet uttryck i argument_list ska vara samma som rangordningen för array_type, och varje uttryck ska vara av typen int, uint, longeller ulong, ska implicit konverteras till en eller flera av dessa typer.

Resultatet av utvärderingen av en matrisåtkomst är en variabel av elementtypen för matrisen, nämligen matriselementet som valts av värdena för uttrycken i argument_list.

Körningsbearbetningen av en matrisåtkomst i formuläret P[A], där P är en primary_no_array_creation_expression av en array_type och A är en argument_list, består av följande steg:

  • P utvärderas. Om den här utvärderingen orsakar ett undantag körs inga ytterligare steg.
  • Indexuttrycken för argument_list utvärderas i ordning, från vänster till höger. Efter utvärdering av varje indexuttryck utförs en implicit konvertering (§10.2) till någon av följande typer: int, uint, long, . ulong Den första typen i den här listan som en implicit konvertering finns för väljs. Om indexuttrycket till exempel är av typen short utförs en implicit konvertering till int , eftersom implicita konverteringar från short till int och från short är long möjliga. Om utvärderingen av ett indexuttryck eller den efterföljande implicita konverteringen orsakar ett undantag utvärderas inga ytterligare indexuttryck och inga ytterligare steg körs.
  • Värdet P för är markerat för att vara giltigt. Om värdet P för är nullgenereras en System.NullReferenceException och inga ytterligare steg körs.
  • Värdet för varje uttryck i argument_list kontrolleras mot de faktiska gränserna för varje dimension i matrisinstansen som refereras av P. Om ett eller flera värden ligger inom intervallet genereras ett System.IndexOutOfRangeException och inga ytterligare steg körs.
  • Platsen för matriselementet som anges av indexuttrycken beräknas och den här platsen blir resultatet av matrisåtkomsten.

12.8.12.3 Indexer-åtkomst

För en indexerares åtkomst ska primary_no_array_creation_expression av element_access vara en variabel eller ett värde för en klass, struct eller gränssnittstyp, och denna typ ska implementera en eller flera indexerare som är tillämpliga för argument_list av element_access.

Bindningstiden för en indexerares åtkomst till formuläret P[A], där P är en primary_no_array_creation_expression av en klass, struct eller gränssnittstyp T, och A är en argument_list, består av följande steg:

  • Uppsättningen indexerare som tillhandahålls av T är konstruerad. Uppsättningen består av alla indexerare som deklareras i T eller en bastyp av T som inte är åsidosättningsdeklarationer och som är tillgängliga i den aktuella kontexten (§7.5).
  • Uppsättningen reduceras till de indexerare som är tillämpliga och inte dolda av andra indexerare. Följande regler tillämpas på varje indexerare S.I i uppsättningen, där S är den typ där indexeraren I deklareras:
    • Om I inte är tillämpligt med avseende A på (§12.6.4.2) tas den I bort från uppsättningen.
    • Om I är tillämpligt med avseende A på (§12.6.4.2) tas alla indexerare som deklarerats i bastypen av S bort från uppsättningen.
    • Om I är tillämpligt med avseende A på (§12.6.4.2) och S är en annan klasstyp än object, tas alla indexerare som deklarerats i ett gränssnitt bort från uppsättningen.
  • Om den resulterande uppsättningen kandidatindexerare är tom finns det inga tillämpliga indexerare och ett bindningstidsfel inträffar.
  • Den bästa indexeraren för uppsättningen kandidatindexerare identifieras med hjälp av reglerna för överbelastningsmatchning i §12.6.4. Om det inte går att identifiera en enda bästa indexerare är indexerarens åtkomst tvetydig och ett bindningstidsfel inträffar.
  • Indexuttrycken för argument_list utvärderas i ordning, från vänster till höger. Resultatet av bearbetningen av indexerarens åtkomst är ett uttryck som klassificeras som en indexerareåtkomst. Indexerarens åtkomstuttryck refererar till indexeraren som fastställdes i steget ovan och har ett associerat instansuttryck för P och en associerad argumentlista med Aoch en associerad typ som är indexerarens typ. Om T är en klasstyp väljs den associerade typen från den första deklarationen eller åsidosättningen av indexeraren som hittas när du börjar med T och söker igenom dess basklasser.

Beroende på i vilken kontext den används orsakar en indexerare åtkomst anrop av antingen get-accessorn eller indexerarens uppsättningsåtkomst. Om indexerarens åtkomst är målet för en tilldelning anropas den angivna åtkomstgivaren för att tilldela ett nytt värde (§12.21.2). I alla andra fall anropas get-accessorn för att hämta det aktuella värdet (§12.2.2).

12.8.13 Null Villkorlig elementåtkomst

En null_conditional_element_access består av en primary_no_array_creation_expression följt av de två tokenerna "?" och "[", följt av en argument_list följt av en "]"-token, följt av noll eller fler dependent_accesses som kan föregås av en null_forgiving_operator.

null_conditional_element_access
    : primary_no_array_creation_expression '?' '[' argument_list ']'
      (null_forgiving_operator? dependent_access)*
    ;

En null_conditional_element_access är en villkorsstyrd version av element_access (§12.8.12) och det är ett bindningstidsfel om resultattypen är void. För ett villkorligt null-uttryck där resultattypen kan visas void (§12.8.11).

Ett null_conditional_element_access uttryck E är av formuläret P?[A]B, där B finns de eventuella dependent_accesses. Innebörden av E bestäms på följande sätt:

  • Om typen av P är en nullbar värdetyp:

    Låt vara T typen av uttrycket P.Value[A]B.

    • Om T är en typparameter som inte är känd för att vara antingen en referenstyp eller en värdetyp som inte kan nulliseras uppstår ett kompileringsfel.

    • Om T är en icke-nullbar värdetyp är T?typen , E och innebörden av E är densamma som innebörden av:

      ((object)P == null) ? (T?)null : P.Value[A]B
      

      Förutom att P utvärderas bara en gång.

    • Annars är Ttypen av E , och innebörden av E är densamma som innebörden av:

      ((object)P == null) ? null : P.Value[A]B
      

      Förutom att P utvärderas bara en gång.

  • Annars:

    Låt vara T typen av uttrycket P[A]B.

    • Om T är en typparameter som inte är känd för att vara antingen en referenstyp eller en värdetyp som inte kan nulliseras uppstår ett kompileringsfel.

    • Om T är en icke-nullbar värdetyp är T?typen , E och innebörden av E är densamma som innebörden av:

      ((object)P == null) ? (T?)null : P[A]B
      

      Förutom att P utvärderas bara en gång.

    • Annars är Ttypen av E , och innebörden av E är densamma som innebörden av:

      ((object)P == null) ? null : P[A]B
      

      Förutom att P utvärderas bara en gång.

Obs! I ett uttryck i formuläret:

P?[A₀]?[A₁]

om P utvärderas till null varken A₀ eller A₁ utvärderas. Detsamma gäller om ett uttryck är en sekvens av null_conditional_element_access eller null_conditional_member_access §12.8.8-åtgärder .

slutkommentar

12.8.14 Den här åtkomsten

En this_access består av nyckelordet this.

this_access
    : 'this'
    ;

En this_access tillåts endast i blocket för en instanskonstruktor, en instansmetod, en instansåtkomstor (§12.2.1) eller en finalizer. Den har någon av följande betydelser:

  • När this används i en primary_expression inom en instanskonstruktor för en klass klassificeras den som ett värde. Typen av värde är instanstypen (§15.3.2) för klassen där användningen sker, och värdet är en referens till objektet som skapas.
  • När this används i en primary_expression inom en instansmetod eller instansåtkomst till en klass klassificeras den som ett värde. Värdets typ är instanstypen (§15.3.2) för klassen där användningen sker, och värdet är en referens till det objekt som metoden eller accessorn anropades för.
  • När this används i en primary_expression inom en instanskonstruktor för en struct klassificeras den som en variabel. Variabeltypen är instanstypen (§15.3.2) för den struct som användningen sker inom, och variabeln representerar den struct som skapas.
    • Om konstruktordeklarationen inte har någon konstruktorinitierare fungerar variabeln this exakt samma som en utdataparameter av typen struct. Detta innebär särskilt att variabeln definitivt ska tilldelas i varje körningsväg för instanskonstruktorn.
    • Annars fungerar variabeln this exakt samma som en ref parameter av struct-typen. I synnerhet innebär det att variabeln anses vara ursprungligen tilldelad.
  • När this används i en primary_expression inom en instansmetod eller instansåtkomst för en struct klassificeras den som en variabel. Variabeltypen är instanstypen (§15.3.2) för den struct inom vilken användningen sker.
    • Om metoden eller accessorn inte är en iterator (§15.14) eller asynkron funktion (§15.15) this representerar variabeln den struct för vilken metoden eller accessorn anropades.
      • Om structen är en readonly structbeter sig variabeln this exakt samma som en indataparameter av structtypen
      • Annars beter sig variabeln this exakt samma som en ref parameter av typen struct
    • Om metoden eller accessorn är en iterator eller asynkron funktion representerar variabeln this en kopia av den struct för vilken metoden eller accessorn anropades och fungerar exakt samma som en värdeparameter av structtypen.

Användning av this i en primary_expression i en annan kontext än de som anges ovan är ett kompileringsfel. I synnerhet är det inte möjligt att referera till this i en statisk metod, en statisk egenskapsåtkomstor eller i en variable_initializer av en fältdeklaration.

12.8.15 Grundläggande åtkomst

En base_access består av nyckelordsbasen följt av antingen en "." token och en identifierare och valfri type_argument_list eller en argument_list inom hakparenteser:

base_access
    : 'base' '.' identifier type_argument_list?
    | 'base' '[' argument_list ']'
    ;

En base_access används för att komma åt basklassmedlemmar som är dolda av medlemmar med liknande namn i den aktuella klassen eller struct. En base_access tillåts endast i brödtexten för en instanskonstruktor, en instansmetod, en instansåtkomstor (§12.2.1) eller en finalizer. När base.I inträffar i en klass eller struct ska jag ange en medlem av basklassen för den klassen eller struct. På samma sätt ska en tillämplig indexerare finnas i basklassen när base[E] den inträffar i en klass.

Vid bindningstid utvärderas base_access uttryck i formuläret base.I och base[E] exakt som om de skrevs ((B)this).I och ((B)this)[E], där B är basklassen för klassen eller structen där konstruktionen inträffar. base.I base[E] Och motsvarar this.I alltså och this[E], förutom this visas som en instans av basklassen.

När en base_access refererar till en virtuell funktionsmedlem (en metod, egenskap eller indexerare) ändras bestämningen av vilken funktionsmedlem som ska anropas vid körning (§12.6.6). Den funktionsmedlem som anropas bestäms genom att hitta den mest härledda implementeringen (§15.6.4) för funktionsmedlemmen med avseende B på (i stället för med avseende på körningstypen thisför , vilket skulle vara vanligt i en icke-basåtkomst). Inom en åsidosättning av en virtuell funktionsmedlem kan därför en base_access användas för att anropa den ärvda implementeringen av funktionsmedlemmen. Om funktionsmedlemmen som refereras av en base_access är abstrakt uppstår ett bindningstidsfel.

Obs! Till skillnad från this, base är inte ett uttryck i sig. Det är ett nyckelord som endast används i samband med en base_access eller en constructor_initializer (§15.11.2). slutkommentar

12.8.16 Postfix-inkrements- och minskningsoperatorer

post_increment_expression
    : primary_expression '++'
    ;

post_decrement_expression
    : primary_expression '--'
    ;

Operand för en postfix-inkrements- eller decrementåtgärd ska vara ett uttryck som klassificeras som en variabel, en egenskapsåtkomst eller en indexerares åtkomst. Resultatet av åtgärden är ett värde av samma typ som operanden.

Om primary_expression har kompileringstidstypen dynamic är operatorn dynamiskt bunden (§12.3.3), post_increment_expression eller post_decrement_expression har kompileringstidstypen dynamic och följande regler tillämpas vid körning med hjälp av körningstypen för primary_expression.

Om operand för en postfix-inkrements- eller decrementåtgärd är en egenskaps- eller indexerareåtkomst, ska egenskapen eller indexeraren ha både en get- och en uppsättningsåtkomst. Om så inte är fallet uppstår ett bindningstidsfel.

Unary operator overload resolution (§12.4.4) tillämpas för att välja en specifik operatorimplementering. Fördefinierade ++ operatorer och -- operatorer finns för följande typer: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, och decimalalla uppräkningstyper. De fördefinierade ++ operatorerna returnerar det värde som produceras genom att lägga 1 till operanden, och de fördefinierade -- operatorerna returnerar det värde som produceras genom att subtrahera 1 från operanden. I en kontrollerad kontext, om resultatet av det här tillägget eller subtraktionen ligger utanför resultattypens intervall och resultattypen är en integrerad typ eller uppräkningstyp, genereras en System.OverflowException .

Det ska ske en implicit konvertering från returtypen för den valda unary-operatorn till typen av primary_expression, annars uppstår ett kompileringsfel.

Körningsbearbetningen av en postfix-inkrements- eller dementeringsåtgärd i formuläret x++ eller x-- består av följande steg:

  • Om x klassificeras som en variabel:
    • x utvärderas för att skapa variabeln.
    • Värdet x för sparas.
    • Det sparade värdet x för konverteras till operandtypen för den valda operatorn och operatorn anropas med det här värdet som argument.
    • Värdet som returneras av operatorn konverteras till typen av x och lagras på den plats som angavs i den tidigare utvärderingen av x.
    • Det sparade värdet x för blir resultatet av åtgärden.
  • Om x klassificeras som en egenskap eller indexerares åtkomst:
    • Instansuttrycket (om x inte static) och argumentlistan (om x är en indexerareåtkomst) som är associerad med x utvärderas och resultaten används i efterföljande get- och set-åtkomstanrop.
    • Get-accessorn x för anropas och det returnerade värdet sparas.
    • Det sparade värdet x för konverteras till operandtypen för den valda operatorn och operatorn anropas med det här värdet som argument.
    • Värdet som returneras av operatorn konverteras till typen av x och den angivna accessorn x för anropas med det här värdet som värdeargument.
    • Det sparade värdet x för blir resultatet av åtgärden.

Operatorerna ++ och -- stöder även prefix notation (§12.9.6). Resultatet av x++ eller är värdet x för före åtgärden, medan resultatet av ++x eller --x är värdet x för efter x-- åtgärden. I båda fallen x har sig själv samma värde efter åtgärden.

En operator ++ - eller operatorimplementering -- kan anropas med antingen postfix eller prefix notation. Det går inte att ha separata operatorimplementeringar för de två notationerna.

12.8.17 Den nya operatorn

12.8.17.1 Allmänt

Operatorn new används för att skapa nya instanser av typer.

Det finns tre former av nya uttryck:

  • Objektskapandeuttryck och anonyma objektskapandeuttryck används för att skapa nya instanser av klasstyper och värdetyper.
  • Uttryck för att skapa matriser används för att skapa nya instanser av matristyper.
  • Uttryck för att skapa ombud används för att hämta instanser av ombudstyper.

Operatorn new innebär att en instans av en typ skapas, men innebär inte nödvändigtvis allokering av minne. I synnerhet kräver instanser av värdetyper inget extra minne utöver de variabler som de finns i, och inga allokeringar sker när new används för att skapa instanser av värdetyper.

Obs! Delegera skapandeuttryck skapar inte alltid nya instanser. När uttrycket bearbetas på samma sätt som en metodgruppkonvertering (§10.8) eller en anonym funktionskonvertering (§10.7) kan det leda till att en befintlig delegatinstans återanvänds. slutkommentar

12.8.17.2 Objektskapandeuttryck

En object_creation_expression används för att skapa en ny instans av en class_type eller en value_type.

object_creation_expression
    : 'new' type '(' argument_list? ')' object_or_collection_initializer?
    | 'new' type object_or_collection_initializer
    ;

object_or_collection_initializer
    : object_initializer
    | collection_initializer
    ;

Typen av object_creation_expression ska vara en class_type, en value_type eller en type_parameter. Typen får inte vara en tuple_type eller en abstrakt eller statisk class_type.

Den valfria argument_list (§12.6.2) är endast tillåten om typen är en class_type eller en struct_type.

Ett objektskapandeuttryck kan utelämna konstruktorns argumentlista och omsluta parenteser förutsatt att det innehåller en objektinitierare eller samlingsinitierare. Att utelämna konstruktorns argumentlista och omsluta parenteser motsvarar att ange en tom argumentlista.

Bearbetning av ett objektskapandeuttryck som innehåller en objektinitierare eller insamlingsinitiator består av att först bearbeta instanskonstruktorn och sedan bearbeta de initieringar av medlemmar eller element som anges av objektinitieraren (§12.8.17.3) eller insamlingsinitiator (§12.8.17.4).

Om något av argumenten i den valfria argument_list har kompileringstidstypen dynamic är object_creation_expression dynamiskt bunden (§12.3.3) och följande regler tillämpas vid körning med hjälp av körningstypen för argumenten i argument_list som har kompileringstidstypen dynamic. Objektskapandet genomgår dock en begränsad kompileringstidskontroll enligt beskrivningen i §12.6.5.

Bindningstiden för en object_creation_expression av formuläret ny T(A), där T är en class_type, eller en value_type och A är en valfri argument_list, består av följande steg:

  • Om T är en value_type och A inte finns:
    • Object_creation_expression är en standardkonstruktoranrop. Resultatet av object_creation_expression är ett värde av typen T, nämligen standardvärdet för T enligt definitionen i §8.3.3.
  • Annars, om T är en type_parameter och A inte finns:
    • Om ingen begränsning av värdetyp eller konstruktor (§15.2.5) har angetts för Tuppstår ett bindningstidsfel.
    • Resultatet av object_creation_expression är ett värde av den körningstyp som typparametern har bundits till, nämligen resultatet av att anropa standardkonstruktorn av den typen. Körningstypen kan vara en referenstyp eller en värdetyp.
  • Annars, om T är en class_type eller en struct_type:
    • Om T är en abstrakt eller statisk class_type uppstår ett kompileringsfel.
    • Instanskonstruktorn som ska anropas bestäms med hjälp av reglerna för överbelastningsmatchning i §12.6.4. Uppsättningen kandidatinstanskonstruktorer består av alla tillgängliga instanskonstruktorer som deklareras i T, som gäller för A (§12.6.4.2). Om uppsättningen kandidatinstanskonstruktorer är tom, eller om en enda bästa instanskonstruktor inte kan identifieras, uppstår ett bindningstidsfel.
    • Resultatet av object_creation_expression är ett värde av typen T, nämligen värdet som skapas genom att anropa instanskonstruktorn som fastställdes i steget ovan.
    • Annars är object_creation_expression ogiltig och ett bindningstidsfel inträffar.

Även om object_creation_expression är dynamiskt bunden är kompileringstidstypen fortfarande T.

Körningsbearbetningen av en object_creation_expression av formuläret ny T(A), där T är class_type eller en struct_type och A är en valfri argument_list, består av följande steg:

  • Om T är en class_type:
    • En ny instans av klassen T allokeras. Om det inte finns tillräckligt med minne tillgängligt för att allokera den nya instansen genereras en System.OutOfMemoryException och inga ytterligare steg körs.
    • Alla fält i den nya instansen initieras till standardvärdena (§9.3).
    • Instanskonstruktorn anropas enligt reglerna för funktionsmedlemsanrop (§12.6.6). En referens till den nyligen allokerade instansen skickas automatiskt till instanskonstruktorn och instansen kan nås inifrån konstruktorn som detta.
  • Om T är en struct_type:
    • En instans av typen T skapas genom att en tillfällig lokal variabel allokeras. Eftersom en instanskonstruktor för en struct_type krävs för att definitivt tilldela ett värde till varje fält i instansen som skapas, krävs ingen initiering av den tillfälliga variabeln.
    • Instanskonstruktorn anropas enligt reglerna för funktionsmedlemsanrop (§12.6.6). En referens till den nyligen allokerade instansen skickas automatiskt till instanskonstruktorn och instansen kan nås inifrån konstruktorn som detta.

12.8.17.3 Objektinitierare

En objektinitierare anger värden för noll eller fler fält, egenskaper eller indexerade element i ett objekt.

object_initializer
    : '{' member_initializer_list? '}'
    | '{' member_initializer_list ',' '}'
    ;

member_initializer_list
    : member_initializer (',' member_initializer)*
    ;

member_initializer
    : initializer_target '=' initializer_value
    ;

initializer_target
    : identifier
    | '[' argument_list ']'
    ;

initializer_value
    : expression
    | object_or_collection_initializer
    ;

En objektinitierare består av en sekvens av medlemsinitierare som omges av { och } token och avgränsas med kommatecken. Varje member_initializer ska ange ett mål för initieringen. En identifierare ska namnge ett tillgängligt fält eller en egenskap för objektet som initieras, medan en argument_list inom hakparenteser ska ange argument för en tillgänglig indexerare för objektet som initieras. Det är ett fel för en objektinitierare att inkludera fler än en medlemsinitierare för samma fält eller egenskap.

Obs! Även om en objektinitierare inte tillåts ange samma fält eller egenskap mer än en gång, finns det inga sådana begränsningar för indexerare. En objektinitierare kan innehålla flera initialiserarmål som refererar till indexerare och kan till och med använda samma indexeringsargument flera gånger. slutkommentar

Varje initializer_target följs av ett likhetstecken och antingen ett uttryck, en objektinitierare eller en insamlingsinitiator. Det är inte möjligt för uttryck i objektinitieraren att referera till det nyligen skapade objektet som initieras.

En medlemsinitierare som anger ett uttryck efter att likhetstecknet bearbetas på samma sätt som en tilldelning (§12.21.2) till målet.

En medlemsinitierare som anger en objektinitierare efter likhetstecknet är en kapslad objektinitierare, dvs. en initiering av ett inbäddat objekt. I stället för att tilldela ett nytt värde till fältet eller egenskapen behandlas tilldelningarna i den kapslade objektinitieraren som tilldelningar till medlemmar i fältet eller egenskapen. Kapslade objektinitierare kan inte tillämpas på egenskaper med en värdetyp eller skrivskyddade fält med en värdetyp.

En medlemsinitierare som anger en insamlingsinitierare efter likhetstecknet är en initiering av en inbäddad samling. I stället för att tilldela en ny samling till målfältet, egenskapen eller indexeraren läggs elementen som anges i initieraren till i samlingen som refereras av målet. Målet ska vara av en samlingstyp som uppfyller kraven i §12.8.17.4.

När ett initialiserarmål refererar till en indexerare ska argumenten till indexeraren alltid utvärderas exakt en gång. Även om argumenten aldrig används (t.ex. på grund av en tom kapslad initiator) utvärderas de för sina biverkningar.

Exempel: Följande klass representerar en punkt med två koordinater:

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

En instans av Point kan skapas och initieras på följande sätt:

Point a = new Point { X = 0, Y = 1 };

Detta har samma effekt som

Point __a = new Point();
__a.X = 0;
__a.Y = 1;
Point a = __a;

där __a är en i övrigt osynlig och otillgänglig tillfällig variabel.

I följande klass visas en rektangel som skapats från två punkter och skapande och initiering av en Rectangle instans:

public class Rectangle
{
    public Point P1 { get; set; }
    public Point P2 { get; set; }
}

En instans av Rectangle kan skapas och initieras på följande sätt:

Rectangle r = new Rectangle
{
    P1 = new Point { X = 0, Y = 1 },
    P2 = new Point { X = 2, Y = 3 }
};

Detta har samma effekt som

Rectangle __r = new Rectangle();
Point __p1 = new Point();
__p1.X = 0;
__p1.Y = 1;
__r.P1 = __p1;
Point __p2 = new Point();
__p2.X = 2;
__p2.Y = 3;
__r.P2 = __p2;
Rectangle r = __r;

där __r, __p1 och __p2 är tillfälliga variabler som annars är osynliga och otillgängliga.

Om Rectanglekonstruktorn allokerar de två inbäddade Point instanserna kan de användas för att initiera de inbäddade Point instanserna i stället för att tilldela nya instanser:

public class Rectangle
{
    public Point P1 { get; } = new Point();
    public Point P2 { get; } = new Point();
}

Följande konstruktion kan användas för att initiera de inbäddade Point instanserna i stället för att tilldela nya instanser:

Rectangle r = new Rectangle
{
    P1 = { X = 0, Y = 1 },
    P2 = { X = 2, Y = 3 }
};

Detta har samma effekt som

Rectangle __r = new Rectangle();
__r.P1.X = 0;
__r.P1.Y = 1;
__r.P2.X = 2;
__r.P2.Y = 3;
Rectangle r = __r;

slutexempel

12.8.17.4 Insamlingsinitierare

En insamlingsinitierare anger elementen i en samling.

collection_initializer
    : '{' element_initializer_list '}'
    | '{' element_initializer_list ',' '}'
    ;

element_initializer_list
    : element_initializer (',' element_initializer)*
    ;

element_initializer
    : non_assignment_expression
    | '{' expression_list '}'
    ;

expression_list
    : expression
    | expression_list ',' expression
    ;

En insamlingsinitierare består av en sekvens av elementinitierare, omgivna av { och } token och avgränsade med kommatecken. Varje elementinitierare anger ett element som ska läggas till i samlingsobjektet som initieras och består av en lista över uttryck som omges av { och } token och avgränsas med kommatecken. En elementinitierare med ett enda uttryck kan skrivas utan klammerparenteser, men kan då inte vara ett tilldelningsuttryck, för att undvika tvetydighet med medlemsinitierare. Den non_assignment_expression produktionen definieras i §12.22.

Exempel: Följande är ett exempel på ett uttryck för att skapa objekt som innehåller en samlingsinitierare:

List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

slutexempel

Samlingsobjektet som en insamlingsinitierare tillämpas på ska vara av en typ som implementerar System.Collections.IEnumerable eller ett kompileringsfel inträffar. För varje angivet element i ordning från vänster till höger tillämpas normal medlemssökning för att hitta en medlem med namnet Add. Om resultatet av medlemssökningen inte är en metodgrupp uppstår ett kompileringsfel. Annars tillämpas överbelastningsmatchning med uttryckslistan för elementinitieraren som argumentlista, och insamlingsinitieraren anropar den resulterande metoden. Samlingsobjektet ska därför innehålla en tillämplig instans- eller tilläggsmetod med namnet Add på varje elementinitierare.

Exempel: Följande visar en klass som representerar en kontakt med ett namn och en lista med telefonnummer samt skapandet och initieringen av en List<Contact>:

public class Contact
{
    public string Name { get; set; }
    public List<string> PhoneNumbers { get; } = new List<string>();
}

class A
{
    static void M()
    {
        var contacts = new List<Contact>
        {
            new Contact
            {
                Name = "Chris Smith",
                PhoneNumbers = { "206-555-0101", "425-882-8080" }
            },
            new Contact
            {
                Name = "Bob Harris",
                PhoneNumbers = { "650-555-0199" }
            }
        };
    }
}

som har samma effekt som

var __clist = new List<Contact>();
Contact __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
__clist.Add(__c1);
Contact __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
__clist.Add(__c2);
var contacts = __clist;

där __clist, __c1 och __c2 är tillfälliga variabler som annars är osynliga och otillgängliga.

slutexempel

12.8.17.5 Matrisskapande uttryck

En array_creation_expression används för att skapa en ny instans av en array_type.

array_creation_expression
    : 'new' non_array_type '[' expression_list ']' rank_specifier*
      array_initializer?
    | 'new' array_type array_initializer
    | 'new' rank_specifier array_initializer
    ;

Ett uttryck för att skapa matriser i det första formuläret allokerar en matrisinstans av den typ som är resultatet av att ta bort vart och ett av de enskilda uttrycken från uttryckslistan.

Exempel: Uttrycket för att skapa new int[10,20] matrisen genererar en matrisinstans av typen int[,], och det nya int[10][,] matrisskapande uttrycket genererar en matrisinstans av typen int[][,]. slutexempel

Varje uttryck i uttryckslistan ska vara av typen int, uint, longeller ulongeller implicit konvertibelt till en eller flera av dessa typer. Värdet för varje uttryck avgör längden på motsvarande dimension i den nyligen allokerade matrisinstansen. Eftersom längden på en matrisdimension ska vara icke-negativt är det ett kompileringsfel att ha ett konstant uttryck med ett negativt värde i uttryckslistan.

Förutom i ett osäkert sammanhang (§23.2) är layouten för matriser ospecificerad.

Om ett uttryck för att skapa matriser i det första formuläret innehåller en matrisinitierare ska varje uttryck i uttryckslistan vara en konstant och de rang- och dimensionslängder som anges i uttryckslistan ska matcha matrisinitierarens.

I ett uttryck för matrisskapande av det andra eller tredje formuläret ska rangordningen för den angivna matristypen eller rangspecificeraren matcha matrisinitierarens. De enskilda dimensionslängderna härleds från antalet element i var och en av motsvarande kapslingsnivåer i matrisinitieraren. Initieringsuttrycket i följande deklaration

var a = new int[,] {{0, 1}, {2, 3}, {4, 5}};

motsvarar exakt

var a = new int[3, 2] {{0, 1}, {2, 3}, {4, 5}};

Ett uttryck för att skapa matriser i det tredje formuläret kallas för ett implicit skrivet uttryck för skapande av matriser. Det liknar det andra formuläret, förutom att elementtypen för matrisen inte uttryckligen anges, utan bestäms som den vanligaste typen (§12.6.3.15) av uppsättningen uttryck i matrisinitieraren. För en flerdimensionell matris, dvs. en där rank_specifier innehåller minst ett kommatecken, består den här uppsättningen av alla uttrycksom finns i kapslade array_initializers.

Matrisinitierare beskrivs ytterligare i §17.7.

Resultatet av utvärderingen av ett uttryck för skapande av matris klassificeras som ett värde, nämligen en referens till den nyligen allokerade matrisinstansen. Körningsbearbetningen av ett uttryck för matrisskapande består av följande steg:

  • Dimensionslängduttrycken för expression_list utvärderas i ordning, från vänster till höger. Efter utvärdering av varje uttryck utförs en implicit konvertering (§10.2) till någon av följande typer: int, uint, long, ulong. Den första typen i den här listan som en implicit konvertering finns för väljs. Om utvärderingen av ett uttryck eller den efterföljande implicita konverteringen orsakar ett undantag utvärderas inga ytterligare uttryck och inga ytterligare steg utförs.
  • De beräknade värdena för dimensionslängderna verifieras enligt följande: Om ett eller flera av värdena är mindre än noll genereras en System.OverflowException och inga ytterligare steg körs.
  • En matrisinstans med angivna dimensionslängder allokeras. Om det inte finns tillräckligt med minne tillgängligt för att allokera den nya instansen genereras en System.OutOfMemoryException och inga ytterligare steg körs.
  • Alla element i den nya matrisinstansen initieras till deras standardvärden (§9.3).
  • Om matrisens skapandeuttryck innehåller en matrisinitierare utvärderas varje uttryck i matrisinitieraren och tilldelas motsvarande matriselement. Utvärderingarna och tilldelningarna utförs i den ordning som uttrycken skrivs i matrisinitieraren, med andra ord initieras elementen i ökad indexordning, och dimensionen längst till höger ökar först. Om utvärderingen av ett givet uttryck eller den efterföljande tilldelningen till motsvarande matriselement orsakar ett undantag initieras inga ytterligare element (och de återstående elementen har därmed sina standardvärden).

Ett uttryck för att skapa matriser tillåter instansiering av en matris med element av en matristyp, men elementen i en sådan matris ska initieras manuellt.

Exempel: Instruktionen

int[][] a = new int[100][];

skapar en endimensionell matris med 100 element av typen int[]. Det initiala värdet för varje element är null. Det går inte att skapa samma matrisuttryck även genom att instansiera undermatriserna och -instruktionen

int[][] a = new int[100][5]; // Error

resulterar i ett kompileringsfel. Instansiering av undermatriserna kan i stället utföras manuellt, som i

int[][] a = new int[100][];
for (int i = 0; i < 100; i++)
{
    a[i] = new int[5];
}

slutexempel

Obs! När en matris med matriser har en "rektangulär" form, dvs. när alla undermatriser har samma längd, är det mer effektivt att använda en flerdimensionell matris. I exemplet ovan skapar instansiering av matrisen med matriser 101 objekt – en yttre matris och 100 undermatriser. Däremot

int[,] a = new int[100, 5];

skapar bara ett enskilt objekt, en tvådimensionell matris, och utför allokeringen i en enda instruktion.

slutkommentar

Exempel: Följande är exempel på implicit skrivna uttryck för skapande av matriser:

var a = new[] { 1, 10, 100, 1000 };                     // int[]
var b = new[] { 1, 1.5, 2, 2.5 };                       // double[]
var c = new[,] { { "hello", null }, { "world", "!" } }; // string[,]
var d = new[] { 1, "one", 2, "two" };                   // Error

Det sista uttrycket orsakar ett kompileringsfel eftersom varken int eller string är implicit konvertibelt till det andra, så det finns ingen vanlig typ. Ett explicit skrivet matrisskapandeuttryck måste användas i det här fallet, till exempel ange vilken typ som ska vara object[]. Alternativt kan ett av elementen gjutas till en gemensam bastyp, som sedan skulle bli den härledda elementtypen.

slutexempel

Implicit skrivna matrisskapandeuttryck kan kombineras med anonyma objektinitierare (§12.8.17.7) för att skapa anonymt inskrivna datastrukturer.

Exempel:

var contacts = new[]
{
    new
    {
        Name = "Chris Smith",
        PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
    },
    new 
    {
        Name = "Bob Harris",
       PhoneNumbers = new[] { "650-555-0199" }
    }
};

slutexempel

12.8.17.6 Delegera skapandeuttryck

En delegate_creation_expression används för att hämta en instans av en delegate_type.

delegate_creation_expression
    : 'new' delegate_type '(' expression ')'
    ;

Argumentet för ett uttryck för skapande av ombud ska vara en metodgrupp, en anonym funktion eller ett värde av antingen kompileringstidstypen dynamic eller en delegate_type. Om argumentet är en metodgrupp identifierar det metoden och för en instansmetod det objekt som du vill skapa ett ombud för. Om argumentet är en anonym funktion definierar det direkt parametrarna och metodtexten för ombudsmålet. Om argumentet är ett värde identifierar det en delegerad instans som du kan skapa en kopia av.

Om uttrycket har kompileringstidstypen dynamictillämpas delegate_creation_expression dynamiskt (§12.8.17.6) och reglerna nedan tillämpas vid körning med uttryckets körningstyp. Annars tillämpas reglerna vid kompileringstid.

Bindningstidsbearbetningen av en delegate_creation_expression av formuläret ny D(E), där D är en delegate_type och E är ett uttryck, består av följande steg:

  • Om E är en metodgrupp bearbetas uttrycket för att skapa ombud på samma sätt som en metodgruppkonvertering (§10.8) från E till D.

  • Om E är en anonym funktion bearbetas uttrycket för att skapa ombud på samma sätt som en anonym funktionskonvertering (§10.7) från E till D.

  • Om E är ett värde ska E vara kompatibelt (§20.2) med D, och resultatet är en referens till ett nyligen skapat ombud med en lista över anrop med en enda post som anropar E.

Körningsbearbetningen av en delegate_creation_expression av formuläret ny D(E), där D är en delegate_type och E är ett uttryck, består av följande steg:

  • Om E är en metodgrupp utvärderas uttrycket för att skapa ombud som en metodgruppkonvertering (§10.8) från E till D.
  • Om E är en anonym funktion utvärderas skapandet av ombudet som en anonym funktionskonvertering från E till D (§10.7).
  • Om E är ett värde för en delegate_type:
    • E utvärderas. Om den här utvärderingen orsakar ett undantag körs inga ytterligare steg.
    • Om värdet E för är nullgenereras en System.NullReferenceException och inga ytterligare steg körs.
    • En ny instans av ombudstypen D allokeras. Om det inte finns tillräckligt med minne tillgängligt för att allokera den nya instansen genereras en System.OutOfMemoryException och inga ytterligare steg körs.
    • Den nya delegatinstansen initieras med en anropslista med en enda post som anropar E.

Anropslistan för ett ombud bestäms när ombudet instansieras och förblir sedan konstant under hela ombudets livslängd. Med andra ord går det inte att ändra målanropsbara entiteter för ett ombud när det har skapats.

Obs! Kom ihåg att när två ombud kombineras eller en tas bort från en annan, ett nytt delegatresultat. Inget befintligt ombud har ändrat sitt innehåll. slutkommentar

Det går inte att skapa ett ombud som refererar till en egenskap, indexerare, användardefinierad operator, instanskonstruktor, finalizer eller statisk konstruktor.

Exempel: Enligt beskrivningen ovan avgör parameterlistan och returtypen för ombudet vilken av de överlagrade metoderna som ska väljas när ett ombud skapas från en metodgrupp. I exemplet

delegate double DoubleFunc(double x);

class A
{
    DoubleFunc f = new DoubleFunc(Square);

    static float Square(float x) => x * x;
    static double Square(double x) => x * x;
}

fältet A.f initieras med ett ombud som refererar till den andra Square metoden eftersom den metoden exakt matchar parameterlistan och returnerar typen av DoubleFunc. Om den andra Square metoden inte hade funnits skulle ett kompileringsfel ha uppstått.

slutexempel

12.8.17.7 Anonyma objektskapandeuttryck

En anonymous_object_creation_expression används för att skapa ett objekt av anonym typ.

anonymous_object_creation_expression
    : 'new' anonymous_object_initializer
    ;

anonymous_object_initializer
    : '{' member_declarator_list? '}'
    | '{' member_declarator_list ',' '}'
    ;

member_declarator_list
    : member_declarator (',' member_declarator)*
    ;

member_declarator
    : simple_name
    | member_access
    | null_conditional_projection_initializer
    | base_access
    | identifier '=' expression
    ;

En anonym objektinitierare deklarerar en anonym typ och returnerar en instans av den typen. En anonym typ är en namnlös klasstyp som ärver direkt från object. Medlemmar av en anonym typ är en sekvens med skrivskyddade egenskaper som härleds från den anonyma objektinitieraren som används för att skapa en instans av typen. Mer specifikt en anonym objektinitierare av formuläret

new {p₁ = e₁ , p₂ = e₂ ,pv = ev }

deklarerar en anonym typ av formuläret

class __Anonymous1
{
    private readonly «T1» «f1»;
    private readonly «T2» «f2»;
    ...
    private readonly «Tn» «fn»;

    public __Anonymous1(«T1» «a1», «T2» «a2»,..., «Tn» «an»)
    {
        «f1» = «a1»;
        «f2» = «a2»;
        ...
        «fn» = «an»;
    }

    public «T1» «p1» { get { return «f1»; } }
    public «T2» «p2» { get { return «f2»; } }
    ...
    public «Tn» «pn» { get { return «fn»; } }
    public override bool Equals(object __o) { ... }
    public override int GetHashCode() { ... }
}

där varje «Tx» är typen av motsvarande uttryck «ex». Uttrycket som används i en member_declarator ska ha en typ. Det är alltså ett kompileringsfel för ett uttryck i en member_declarator vara null eller en anonym funktion.

Namnen på en anonym typ och parametern till metoden Equals genereras automatiskt av kompilatorn och kan inte refereras i programtext.

I samma program skapar två anonyma objektinitierare som anger en sekvens med egenskaper för samma namn och kompileringstidstyper i samma ordning instanser av samma anonyma typ.

Exempel: I exemplet

var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;

tilldelningen på den sista raden tillåts eftersom p1 och p2 är av samma anonyma typ.

slutexempel

Metoderna Equals och GetHashcode för anonyma typer åsidosätter de metoder som ärvts från objectoch definieras i termer av Equals egenskaperna och GetHashcode så att två instanser av samma anonyma typ är lika om och endast om alla deras egenskaper är lika.

En medlemsdeklarator kan förkortas till ett enkelt namn (§12.8.4), en medlemsåtkomst (§12.8.7), en null villkorlig projektion initializer §12.8.8 eller en basåtkomst (§12.8.15). Detta kallas projektioninitierare och är en förkortning för en deklaration av och tilldelning till en egenskap med samma namn. Mer specifikt, medlemsdeklaratorer av formulären

«identifier», «expr» . «identifier» och «expr» ? . «identifier»

är exakt likvärdiga med följande:

«identifer» = «identifier», «identifier» = «expr» . «identifier» och «identifier» = «expr» ? . «identifier»

I en projektionsinitiering väljer identifieraren därför både värdet och det fält eller den egenskap som värdet tilldelas till. Intuitivt projekterar en projektion initierare inte bara ett värde, utan även namnet på värdet.

12.8.18 Typ av operator

Operatorn typeof används för att hämta System.Type objektet för en typ.

typeof_expression
    : 'typeof' '(' type ')'
    | 'typeof' '(' unbound_type_name ')'
    | 'typeof' '(' 'void' ')'
    ;

unbound_type_name
    : identifier generic_dimension_specifier?
    | identifier '::' identifier generic_dimension_specifier?
    | unbound_type_name '.' identifier generic_dimension_specifier?
    ;

generic_dimension_specifier
    : '<' comma* '>'
    ;

comma
    : ','
    ;

Den första formen av typeof_expression består av ett typeof nyckelord följt av en parentestyp. Resultatet av ett uttryck för det här formuläret är objektet System.Type för den angivna typen. Det finns bara ett System.Type objekt för en viss typ. Det innebär att för en typ Tär alltid typeof(T) == typeof(T) sant. Typen får inte vara dynamic.

Den andra formen av typeof_expression består av ett typeof nyckelord följt av en parenteserad unbound_type_name.

Obs! En unbound_type_name liknar en type_name (§7.8) förutom att en unbound_type_name innehåller generic_dimension_specifierdär en type_name innehåller type_argument_lists. slutkommentar

När operand för en typeof_expression är en sekvens av token som uppfyller grammatikerna i både unbound_type_name och type_name, nämligen när den varken innehåller en generic_dimension_specifier eller en type_argument_list, anses sekvensen av token vara en type_name. Innebörden av en unbound_type_name bestäms på följande sätt:

  • Konvertera sekvensen med token till en type_name genom att ersätta varje generic_dimension_specifier med en type_argument_list med samma antal kommatecken och nyckelordet object som varje type_argument.
  • Utvärdera den resulterande type_name, samtidigt som du ignorerar alla typparameterbegränsningar.
  • Den unbound_type_name matchar den obundna generiska typen som är associerad med den resulterande konstruerade typen (§8.4).

Det är ett fel när typnamnet ska vara en nullbar referenstyp.

Resultatet av typeof_expression är objektet System.Type för den resulterande obundna generiska typen.

Den tredje formen av typeof_expression består av ett typeof nyckelord följt av ett parentesiserat void nyckelord. Resultatet av ett uttryck för det här formuläret är det System.Type objekt som representerar frånvaron av en typ. Typobjektet som returneras av typeof(void) skiljer sig från det typobjekt som returneras för alla typer.

Obs! Det här specialobjektet System.Type är användbart i klassbibliotek som tillåter reflektion till metoder på språket, där dessa metoder vill ha ett sätt att representera returtypen för alla metoder, inklusive void metoder, med en instans av System.Type. slutkommentar

Operatorn typeof kan användas på en typparameter. Det är ett kompileringstidsfel om typnamnet är känt som en nullbar referenstyp. Resultatet är objektet System.Type för den körningstyp som var bunden till typparametern. Om körningstypen är en referenstyp som kan ogiltigförklaras blir resultatet motsvarande referenstyp som inte går att nolla. Operatorn typeof kan också användas på en konstruerad typ eller en obunden allmän typ (§8.4.4). Objektet System.Type för en obundna generisk typ är inte detsamma som System.Type objektet av instanstypen (§15.3.2). Instanstypen är alltid en sluten konstruktionstyp vid körning, så objektet System.Type beror på vilka argument av körningstyp som används. Den obundna generiska typen har däremot inga typargument och ger samma System.Type objekt oavsett argument av körningstyp.

Exempel: Exemplet

class X<T>
{
    public static void PrintTypes()
    {
        Type[] t =
        {
            typeof(int),
            typeof(System.Int32),
            typeof(string),
            typeof(double[]),
            typeof(void),
            typeof(T),
            typeof(X<T>),
            typeof(X<X<T>>),
            typeof(X<>)
        };
        for (int i = 0; i < t.Length; i++)
        {
            Console.WriteLine(t[i]);
        }
    }
}

class Test
{
    static void Main()
    {
        X<int>.PrintTypes();
    }
}

genererar följande utdata:

System.Int32
System.Int32
System.String
System.Double[]
System.Void
System.Int32
X`1[System.Int32]
X`1[X`1[System.Int32]]
X`1[T]

Observera att int och System.Int32 är av samma typ. Resultatet av typeof(X<>) beror inte på typargumentet, men resultatet av typeof(X<T>) gör det.

slutexempel

12.8.19 Operatorns storlek

Operatorn sizeof returnerar antalet 8-bitars byte som används av en variabel av en viss typ. Den typ som anges som en operand till sizeof ska vara en unmanaged_type (§8.8).

sizeof_expression
    : 'sizeof' '(' unmanaged_type ')'
    ;

För vissa fördefinierade typer ger operatorn sizeof ett konstant int värde enligt tabellen nedan:

Expression Resultat
sizeof(sbyte) 1
sizeof(byte) 1
sizeof(short) 2
sizeof(ushort) 2
sizeof(int) 4
sizeof(uint) 4
sizeof(long) 8
sizeof(ulong) 8
sizeof(char) 2
sizeof(float) 4
sizeof(double) 8
sizeof(bool) 1
sizeof(decimal) 16

För en uppräkningstyp Tär resultatet av uttrycket sizeof(T) ett konstant värde som är lika med storleken på dess underliggande typ, enligt ovan. För alla andra operandtyper anges operatorn sizeof i §23.6.9.

12.8.20 De markerade och okontrollerade operatorerna

Operatorerna checked och unchecked används för att styra överflödeskontrollkontexten för aritmetiska åtgärder av typen integraltyp och konverteringar.

checked_expression
    : 'checked' '(' expression ')'
    ;

unchecked_expression
    : 'unchecked' '(' expression ')'
    ;

Operatorn checked utvärderar det inneslutna uttrycket i en kontrollerad kontext och operatorn unchecked utvärderar det inneslutna uttrycket i en omarkerad kontext. En checked_expression eller unchecked_expression motsvarar exakt en parenthesized_expression (§12.8.5), förutom att det inneslutna uttrycket utvärderas i den angivna overflow-kontrollkontexten.

Kontrollkontexten för spill kan också styras via instruktionen checked och unchecked (§13.12).

Följande åtgärder påverkas av den överflödeskontrollkontext som har upprättats av de markerade och okontrollerade operatorerna och -instruktionerna:

  • De fördefinierade ++ operatorerna och -- operatorerna (§12.8.16 och §12.9.6), när operanden är av en integral- eller uppräkningstyp.
  • Den fördefinierade - unary operatorn (§12.9.3), när operanden är av en integraltyp.
  • De fördefinierade +operatorerna , -, *och / binära operatorer (§12.10), när båda operanderna är av integral- eller uppräkningstyper.
  • Explicita numeriska konverteringar (§10.3.2) från en integral- eller uppräkningstyp till en annan integral- eller uppräkningstyp, eller från float eller double till en integral- eller uppräkningstyp.

När någon av ovanstående åtgärder ger ett resultat som är för stort för att representera i måltypen, styr kontexten där åtgärden utförs det resulterande beteendet:

  • Om åtgärden i ett checked sammanhang är ett konstant uttryck (§12.23) uppstår ett kompileringsfel. Annars genereras en System.OverflowException när åtgärden utförs vid körning.
  • I ett unchecked sammanhang trunkeras resultatet genom att alla högordningsbitar som inte får plats i måltypen ignoreras.

För icke-konstanta uttryck (§12.23) (uttryck som utvärderas vid körning) som inte omges av några checked operatorer eller unchecked instruktioner är standardkontexten för kontroll av spill avmarkerad, såvida inte externa faktorer (till exempel kompilatorväxlar och konfiguration av körningsmiljön) anropar för kontrollerad utvärdering.

För konstanta uttryck (§12.23) (uttryck som kan utvärderas fullständigt vid kompileringstid) kontrolleras alltid standardkontexten för spillkontroll. Om inte ett konstant uttryck uttryckligen placeras i en unchecked kontext orsakar spill som uppstår under kompileringstidsutvärderingen av uttrycket alltid kompileringstidsfel.

Brödtexten för en anonym funktion påverkas inte av checked eller unchecked kontexter där den anonyma funktionen inträffar.

Exempel: I följande kod

class Test
{
    static readonly int x = 1000000;
    static readonly int y = 1000000;

    static int F() => checked(x * y);    // Throws OverflowException
    static int G() => unchecked(x * y);  // Returns -727379968
    static int H() => x * y;             // Depends on default
}

inga kompileringsfel rapporteras eftersom inget av uttrycken kan utvärderas vid kompileringstid. Vid körningen F genererar metoden en System.OverflowException, och G metoden returnerar –727379968 (de lägre 32 bitarna i resultatet som ligger utom intervallet). Metodens beteende H beror på standardkontexten för spillkontroll för kompilering, men den är antingen samma som F eller samma som G.

slutexempel

Exempel: I följande kod

class Test
{
    const int x = 1000000;
    const int y = 1000000;

    static int F() => checked(x * y);    // Compile-time error, overflow
    static int G() => unchecked(x * y);  // Returns -727379968
    static int H() => x * y;             // Compile-time error, overflow
}

de spill som uppstår när du utvärderar konstanta uttryck i F och H orsakar kompileringstidsfel som rapporteras eftersom uttrycken utvärderas i en checked kontext. Ett spill uppstår också när du utvärderar det konstanta uttrycket i G, men eftersom utvärderingen sker i en unchecked kontext rapporteras inte spillet.

slutexempel

Operatorerna checked och unchecked påverkar endast overflow-kontrollkontexten för de åtgärder som finns i text i tokenerna "(" och ")". Operatorerna har ingen effekt på funktionsmedlemmar som anropas till följd av utvärderingen av det inneslutna uttrycket.

Exempel: I följande kod

class Test
{
    static int Multiply(int x, int y) => x * y;

    static int F() => checked(Multiply(1000000, 1000000));
}

användningen av checked i F påverkar inte utvärderingen av x * y i Multiply, så x * y utvärderas i standardöverflödeskontrollkontexten.

slutexempel

Operatorn unchecked är praktisk när du skriver konstanter av de signerade integraltyperna i hexadecimal notation.

Exempel:

class Test
{
    public const int AllBits = unchecked((int)0xFFFFFFFF);
    public const int HighBit = unchecked((int)0x80000000);
}

Båda hexadecimala konstanterna ovan är av typen uint. Eftersom konstanterna ligger utanför int intervallet, utan operatorn unchecked , skulle gjutningarna till int generera kompileringsfel.

slutexempel

Obs! Operatorerna checked och unchecked -instruktionerna gör det möjligt för programmerare att kontrollera vissa aspekter av vissa numeriska beräkningar. Beteendet för vissa numeriska operatorer beror dock på deras operanders datatyper. Om du till exempel multiplicerar två decimaler resulterar det alltid i ett undantag vid spill även inom en explicit omarkerad konstruktion. På samma sätt resulterar multiplicerande av två flyttal aldrig i ett undantag vid spill även inom en explicit kontrollerad konstruktion. Dessutom påverkas inte andra operatorer av kontrollläget, oavsett om de är standard eller explicita. slutkommentar

12.8.21 Standardvärdeuttryck

Ett standardvärdeuttryck används för att hämta standardvärdet (§9.3) av en typ.

default_value_expression
    : explictly_typed_default
    | default_literal
    ;

explictly_typed_default
    : 'default' '(' type ')'
    ;

default_literal
    : 'default'
    ;

Ett default_literal representerar ett standardvärde (§9.3). Den har ingen typ, men kan konverteras till vilken typ som helst genom en standardliteralkonvertering (§10.2.16).

Resultatet av en default_value_expression är standardvärdet (§9.3) av den explicita typen i en explictly_typed_default eller måltypen för konverteringen för en default_value_expression.

Ett default_value_expression är ett konstant uttryck (§12.23) om typen är en av:

  • en referenstyp
  • en typparameter som är känd för att vara en referenstyp (§8.2);
  • någon av följande värdetyper: , , , , , , , long, ulong, char, float, double, , decimal, bool,eller intuintushortshortbytesbyte
  • någon uppräkningstyp.

12.8.22 Stack-allokering

Ett stackallokeringsuttryck allokerar ett minnesblock från körningsstacken. Körningsstacken är ett minnesområde där lokala variabler lagras. Körningsstacken är inte en del av den hanterade heapen. Det minne som används för lokal variabellagring återställs automatiskt när den aktuella funktionen returneras.

De säkra kontextreglerna för ett stackallokeringsuttryck beskrivs i §16.4.12.7.

stackalloc_expression
    : 'stackalloc' unmanaged_type '[' expression ']'
    | 'stackalloc' unmanaged_type? '[' constant_expression? ']'
      stackalloc_initializer
    ;

stackalloc_initializer
     : '{' stackalloc_initializer_element_list '}'
     ;

stackalloc_initializer_element_list
     : stackalloc_element_initializer (',' stackalloc_element_initializer)* ','?
     ;
    
stackalloc_element_initializer
    : expression
    ;

En stackalloc_expression tillåts endast i två kontexter:

  1. Initieringsuttrycket , Eav en local_variable_declaration (§13.6.2); och
  2. Det högra operanduttrycket, , av en enkel tilldelning (§12.21.2) som sig själv uppstår som en expression_statement (§13.7) E

I båda sammanhangen tillåts stackalloc_expression endast ske som:

Unmanaged_type (§8.8) anger vilken typ av objekt som ska lagras på den nyligen allokerade platsen, och uttrycket anger antalet objekt. Tillsammans anger dessa den nödvändiga allokeringsstorleken. Uttryckets typ ska implicit konverteras till typen int.

Eftersom storleken på en stackallokering inte kan vara negativ är det ett kompileringsfel att ange antalet objekt som en constant_expression som utvärderas till ett negativt värde.

Vid körningen om antalet objekt som ska allokeras är ett negativt värde är beteendet odefinierat. Om det är noll görs ingen allokering och värdet som returneras är implementeringsdefinierat. Om det inte finns tillräckligt med minne tillgängligt för att allokera objekten genereras ett System.StackOverflowException .

När en stackalloc_initializer finns:

  • Om unmanaged_type utelämnas, härleds det enligt reglerna för bästa vanliga typ (§12.6.3.15) för uppsättningen med stackalloc_element_initializers.
  • Om constant_expression utelämnas härleds det till antalet stackalloc_element_initializers.
  • Om constant_expression finns skall det vara lika med antalet stackalloc_element_initializers.

Varje stackalloc_element_initializer ska ha en implicit konvertering till unmanaged_type (§10.2). Stackalloc_element_initializer initierar element i det allokerade minnet i ökande ordning, med början med elementet vid index noll. I avsaknad av en stackalloc_initializer är innehållet i det nyligen allokerade minnet odefinierat.

Resultatet av en stackalloc_expression är en instans av typen Span<T>, där T är unmanaged_type:

  • Span<T> (§C.3) är en referens struct typ (§16.2.3), som presenterar ett block av minne, här blocket allokeras av stackalloc_expression, som en indexerbar samling av typade (T) objekt.
  • Resultatets Length egenskap returnerar antalet allokerade objekt.
  • Resultatets indexerare (§15.9) returnerar en variable_reference (§9.5) till ett objekt i det allokerade blocket och är intervallkontrollerat.

Obs! Vid inträffar i osäker kod kan resultatet av en stackalloc_expression vara av en annan typ, se (§23.9). slutkommentar

Stackallokeringsinitierare tillåts inte i catch eller block (§13.11finally).

Obs! Det finns inget sätt att uttryckligen frigöra minne som allokerats med .stackalloc slutkommentar

Alla stackallokerade minnesblock som skapades under körningen av en funktionsmedlem ignoreras automatiskt när funktionsmedlemmen returnerar.

Förutom operatorn stackalloc innehåller C# inga fördefinierade konstruktioner för hantering av icke-skräpinsamlat minne. Sådana tjänster tillhandahålls vanligtvis av stöd för klassbibliotek eller importeras direkt från det underliggande operativsystemet.

Exempel:

// Memory uninitialized
Span<int> span1 = stackalloc int[3];
// Memory initialized
Span<int> span2 = stackalloc int[3] { -10, -15, -30 };
// Type int is inferred
Span<int> span3 = stackalloc[] { 11, 12, 13 };
// Error; result is int*, not allowed in a safe context
var span4 = stackalloc[] { 11, 12, 13 };
// Error; no conversion from Span<int> to Span<long>
Span<long> span5 = stackalloc[] { 11, 12, 13 };
// Converts 11 and 13, and returns Span<long> 
Span<long> span6 = stackalloc[] { 11, 12L, 13 };
// Converts all and returns Span<long>
Span<long> span7 = stackalloc long[] { 11, 12, 13 };
// Implicit conversion of Span<T>
ReadOnlySpan<int> span8 = stackalloc int[] { 10, 22, 30 };
// Implicit conversion of Span<T>
Widget<double> span9 = stackalloc double[] { 1.2, 5.6 };

public class Widget<T>
{
    public static implicit operator Widget<T>(Span<double> sp) { return null; }
}

När det gäller span8, stackalloc resulterar i en Span<int>, som konverteras av en implicit operator till ReadOnlySpan<int>. På samma sätt konverteras resultatet Span<double> för span9för den användardefinierade typen Widget<double> med hjälp av konverteringen, som visas. slutexempel

12.8.23 Operatorns namn

En nameof_expression används för att hämta namnet på en programentitet som en konstant sträng.

nameof_expression
    : 'nameof' '(' named_entity ')'
    ;
    
named_entity
    : named_entity_target ('.' identifier type_argument_list?)*
    ;
    
named_entity_target
    : simple_name
    | 'this'
    | 'base'
    | predefined_type 
    | qualified_alias_member
    ;

Eftersom nameof det inte är ett nyckelord är en nameof_expression alltid syntaktiskt tvetydig med ett anrop av det enkla namnet nameof. Om ett namnsökning (§12.8.4) av namnet nameof lyckas av kompatibilitetsskäl behandlas uttrycket som en invocation_expression – oavsett om anropet är giltigt. Annars är det en nameof_expression.

Enkla namn och medlemsåtkomstsökningar utförs på named_entity vid kompileringstid, enligt de regler som beskrivs i §12.8.4 och §12.8.7. Men där uppslag som beskrivs i §12.8.4 och §12.8.7 resulterar i ett fel eftersom en instansmedlem hittades i ett statiskt sammanhang, genererar en nameof_expression inget sådant fel.

Det är ett kompileringsfel för en named_entity som anger att en metodgrupp ska ha en type_argument_list. Det är ett kompileringsfel för en named_entity_target att ha typen dynamic.

Ett nameof_expression är ett konstant uttryck av typen stringoch har ingen effekt vid körning. Mer specifikt utvärderas inte dess named_entity och ignoreras i syfte att få en bestämd tilldelningsanalys (§9.4.4.22). Dess värde är den sista identifieraren för named_entity före den valfria slutgiltiga type_argument_list, transformerad på följande sätt:

  • Prefixet "@", om det används, tas bort.
  • Varje unicode_escape_sequence omvandlas till motsvarande Unicode-tecken.
  • Alla formatting_characters tas bort.

Dessa är samma omvandlingar som tillämpas i §6.4.3 vid testning av likhet mellan identifierare.

Exempel: Följande illustrerar resultatet av olika nameof uttryck, förutsatt att en allmän typ List<T> deklareras inom System.Collections.Generic namnområdet:

using TestAlias = System.String;

class Program
{
    static void Main()
    {
        var point = (x: 3, y: 4);

        string n1 = nameof(System);                      // "System"
        string n2 = nameof(System.Collections.Generic);  // "Generic"
        string n3 = nameof(point);                       // "point"
        string n4 = nameof(point.x);                     // "x"
        string n5 = nameof(Program);                     // "Program"
        string n6 = nameof(System.Int32);                // "Int32"
        string n7 = nameof(TestAlias);                   // "TestAlias"
        string n8 = nameof(List<int>);                   // "List"
        string n9 = nameof(Program.InstanceMethod);      // "InstanceMethod"
        string n10 = nameof(Program.GenericMethod);      // "GenericMethod"
        string n11 = nameof(Program.NestedClass);        // "NestedClass"

        // Invalid
        // string x1 = nameof(List<>);            // Empty type argument list
        // string x2 = nameof(List<T>);           // T is not in scope
        // string x3 = nameof(GenericMethod<>);   // Empty type argument list
        // string x4 = nameof(GenericMethod<T>);  // T is not in scope
        // string x5 = nameof(int);               // Keywords not permitted
        // Type arguments not permitted for method group
        // string x6 = nameof(GenericMethod<Program>);
    }

    void InstanceMethod() { }

    void GenericMethod<T>()
    {
        string n1 = nameof(List<T>); // "List"
        string n2 = nameof(T);       // "T"
    }

    class NestedClass { }
}

Potentiellt överraskande delar av det här exemplet är lösningen nameof(System.Collections.Generic) på till bara "Generic" i stället för det fullständiga namnområdet och nameof(TestAlias) till "TestAlias" i stället för "String". slutexempel

12.8.24 Anonyma metoduttryck

Ett anonymous_method_expression är ett av två sätt att definiera en anonym funktion. Dessa beskrivs ytterligare i §12.19.

12.9 Unary operatorer

12.9.1 Allmänt

Operatorerna +, , -( ! logisk negation §12.9.4 ), ~, ++, --, cast och await operatorer kallas unary operatorer.

Obs! Postfixet null-forgiving-operatorn (§12.8.9), !, på grund av dess kompileringstid och icke-överbelastningsbara endast karaktär, undantas från ovanstående lista. slutkommentar

unary_expression
    : primary_expression
    | '+' unary_expression
    | '-' unary_expression
    | logical_negation_operator unary_expression
    | '~' unary_expression
    | pre_increment_expression
    | pre_decrement_expression
    | cast_expression
    | await_expression
    | pointer_indirection_expression    // unsafe code support
    | addressof_expression              // unsafe code support
    ;

pointer_indirection_expression (§23.6.2) och addressof_expression (§23.6.5) är endast tillgängliga i osäker kod (§23).

Om operand för en unary_expression har kompileringstidstypen dynamicär den dynamiskt bunden (§12.3.3). I det här fallet är dynamickompileringstidstypen för unary_expression , och lösningen som beskrivs nedan sker vid körning med hjälp av körningstypen för operand.

12.9.2 Unary plus operator

För en åtgärd i formuläret +xtillämpas unary operator overload resolution (§12.4.4) för att välja en specifik operatorimplementering. Operand konverteras till parametertypen för den valda operatorn och typen av resultat är operatorns returtyp. De fördefinierade odefinierade plusoperatorerna är:

int operator +(int x);
uint operator +(uint x);
long operator +(long x);
ulong operator +(ulong x);
float operator +(float x);
double operator +(double x);
decimal operator +(decimal x);

För var och en av dessa operatorer är resultatet bara operandens värde.

Lyfts (§12.4.8) bildar av de unlifted fördefinierade unary plus operatörerna som definieras ovan, är också fördefinierade.

12.9.3 Unary minus operator

För en åtgärd i formuläret –xtillämpas unary operator overload resolution (§12.4.4) för att välja en specifik operatorimplementering. Operand konverteras till parametertypen för den valda operatorn och typen av resultat är operatorns returtyp. De fördefinierade unary minus-operatorerna är:

  • Heltals negation:

    int operator –(int x);
    long operator –(long x);
    

    Resultatet beräknas genom att subtrahera X från noll. Om värdera av X är det minsta representable värderar av operandtypen (−2³¹ för int eller −2⁶³ för long), därefter är den matematiska negationen av X inte representable inom operandtypen. Om detta inträffar inom en checked kontext genereras en System.OverflowException . Om det inträffar inom en unchecked kontext är resultatet operandvärdet och spillet rapporteras inte.

    Om negationsoperatorns operand är av typen uintkonverteras den till typ longoch resultatets typ är long. Ett undantag är regeln som tillåter int att värdet −2147483648 (−2³¹) skrivs som en decimal heltalsliteral (§6.4.5.3).

    Om negationsoperatorns operand är av typen ulonguppstår ett kompileringsfel. Ett undantag är regeln som tillåter long att värdet −9223372036854775808 (−2⁶³) skrivs som en decimal heltal (§6.4.5.3)

  • Flyttal negation:

    float operator –(float x);
    double operator –(double x);
    

    Resultatet är värdet för X med dess tecken inverterat. Om x är NaNär resultatet också NaN.

  • Decimal negation:

    decimal operator –(decimal x);
    

    Resultatet beräknas genom att subtrahera X från noll. Decimal negation motsvarar att använda den unary minus operatorn av typen System.Decimal.

Lyfts (§12.4.8) bildar av de unlifted fördefinierade unary minus operatörerna som definieras ovan, är också fördefinierade.

12.9.4 Logisk negationsoperator

För en åtgärd i formuläret !xtillämpas unary operator overload resolution (§12.4.4) för att välja en specifik operatorimplementering. Operand konverteras till parametertypen för den valda operatorn och typen av resultat är operatorns returtyp. Det finns bara en fördefinierad logisk negationsoperator:

bool operator !(bool x);

Den här operatorn beräknar den logiska negationen av operand: Om operand är trueär falseresultatet . Om operand är falseär trueresultatet .

Lyfts (§12.4.8) bildar av den unlifted fördefinierade logiska negationoperatorn som definieras ovan, är också fördefinierade.

Obs! Prefixet logisk negation och postfix null-förlåtande operatorer (§12.8.9), medan de representeras av samma lexikala token (!), är distinkta. slutkommentar

12.9.5 Bitvis komplementoperator

För en åtgärd i formuläret ~xtillämpas unary operator overload resolution (§12.4.4) för att välja en specifik operatorimplementering. Operand konverteras till parametertypen för den valda operatorn och typen av resultat är operatorns returtyp. De fördefinierade bitvis kompletterande operatorerna är:

int operator ~(int x);
uint operator ~(uint x);
long operator ~(long x);
ulong operator ~(ulong x);

För var och en av dessa operatorer är resultatet av åtgärden bitvis komplement av x.

Varje uppräkningstyp E tillhandahåller implicit följande bitvis komplementoperator:

E operator ~(E x);

Resultatet av utvärderingen , där X är ett uttryck för en uppräkningstyp E med en underliggande typ U, är exakt detsamma som att (E)(~(U)x)utvärdera , förutom att konverteringen till E alltid utförs som i ett unchecked sammanhang (§12.8.20).~x

Lyfts (§12.4.8) bildar av de unlifted fördefinierade bitwisekompletteringoperatorerna som definieras ovan, är också fördefinierade.

12.9.6 Inkrements- och minskningsoperatorer för prefix

pre_increment_expression
    : '++' unary_expression
    ;

pre_decrement_expression
    : '--' unary_expression
    ;

Operand för en prefix-inkrements- eller decrementåtgärd ska vara ett uttryck som klassificeras som en variabel, en egenskapsåtkomst eller en indexerares åtkomst. Resultatet av åtgärden är ett värde av samma typ som operanden.

Om operand för en prefix-inkrements- eller decrementåtgärd är en egenskaps- eller indexerareåtkomst, ska egenskapen eller indexeraren ha både en get- och en uppsättningsåtkomst. Om så inte är fallet uppstår ett bindningstidsfel.

Unary operator overload resolution (§12.4.4) tillämpas för att välja en specifik operatorimplementering. Fördefinierade ++ operatorer och -- operatorer finns för följande typer: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, och decimalalla uppräkningstyper. De fördefinierade ++ operatorerna returnerar det värde som produceras genom att lägga 1 till operanden, och de fördefinierade -- operatorerna returnerar det värde som produceras genom att subtrahera 1 från operanden. I ett checked sammanhang, om resultatet av det här tillägget eller subtraktionen ligger utanför resultattypens intervall och resultattypen är en integrerad typ eller uppräkningstyp, genereras en System.OverflowException .

Det ska ske en implicit konvertering från returtypen för den valda unary-operatorn till typen av unary_expression, annars uppstår ett kompileringsfel.

Körningsbearbetningen av ett prefix för inkrement eller minskning av formuläret ++x eller --x består av följande steg:

  • Om x klassificeras som en variabel:
    • x utvärderas för att skapa variabeln.
    • Värdet x för konverteras till operandtypen för den valda operatorn och operatorn anropas med det här värdet som argument.
    • Värdet som returneras av operatorn konverteras till typen x. Det resulterande värdet lagras på den plats som anges av utvärderingen av x.
    • och blir resultatet av åtgärden.
  • Om x klassificeras som en egenskap eller indexerares åtkomst:
    • Instansuttrycket (om x inte static) och argumentlistan (om x är en indexerareåtkomst) som är associerad med x utvärderas och resultaten används i efterföljande get- och set-åtkomstanrop.
    • Get-accessorn x för anropas.
    • Värdet som returneras av get-accessorn konverteras till operandtypen för den valda operatorn och operatorn anropas med det här värdet som argument.
    • Värdet som returneras av operatorn konverteras till typen x. Set-åtkomstorn x för anropas med det här värdet som värdeargument.
    • Det här värdet blir också resultatet av åtgärden.

Operatorerna ++ och -- stöder även postfix notation (§12.8.16). Resultatet av x++ eller är värdet x för före åtgärden, medan resultatet av ++x eller --x är värdet x x-- för efter åtgärden. I båda fallen x har sig själv samma värde efter åtgärden.

En operator ++ - eller operatorimplementering -- kan anropas med antingen postfix eller prefix notation. Det går inte att ha separata operatorimplementeringar för de två notationerna.

Lyfts (§12.4.8) bildar av de unlifted predefined prefixet inkrement och decrementoperatorer som definieras ovan, är också fördefinierade.

12.9.7 Gjutna uttryck

En cast_expression används för att explicit konvertera ett uttryck till en viss typ.

cast_expression
    : '(' type ')' unary_expression
    ;

En cast_expression av formuläret (T)E, där T är en typ och E är en unary_expression, utför en explicit konvertering (§10.3) av värdet E för att skriva T . Om det inte finns någon explicit konvertering från E till Tinträffar ett bindningstidsfel. Annars är resultatet det värde som genereras av den explicita konverteringen. Resultatet klassificeras alltid som ett värde, även om E det anger en variabel.

Grammatiken för en cast_expression leder till vissa syntaktiska tvetydigheter.

Exempel: Uttrycket (x)–y kan antingen tolkas som en cast_expression (en uppsättning –y av typen x) eller som en additive_expression kombinerad med en parenthesized_expression (som beräknar värdet x – y). slutexempel

För att lösa cast_expression tvetydigheter finns följande regel: En sekvens med en eller flera token (§6.4) som omges av parenteser anses vara början på en cast_expression endast om minst ett av följande är sant:

  • Sekvensen med token är korrekt grammatik för en typ, men inte för ett uttryck.
  • Sekvensen av token är korrekt grammatik för en typ, och token omedelbart efter de avslutande parenteserna är token "~", token "!", token "(", en identifierare (§6.4.3), en literal (§6.4.5) eller nyckelord (§6.4.4) förutom as och is.

Termen "korrekt grammatik" ovan innebär endast att sekvensen av token ska överensstämma med den specifika grammatiska produktionen. Den tar särskilt inte hänsyn till den faktiska innebörden av några komponenter.

Exempel: Om x och y är identifierare är det x.y korrekt grammatik för en typ, även om x.y det faktiskt inte anger en typ. slutexempel

Obs! Från disambiguationsregeln följer det att om x och y är identifierare, (x)y, (x)(y)och (x)(-y) är cast_expressions, men (x)-y inte är det, även om x identifierar en typ. Men om x är ett nyckelord som identifierar en fördefinierad typ (till exempel int), är alla fyra formulären cast_expressions (eftersom ett sådant nyckelord inte kan vara ett uttryck av sig självt). slutkommentar

12.9.8 Vänta på uttryck

12.9.8.1 Allmänt

Operatorn await används för att pausa utvärderingen av den omslutande asynkrona funktionen tills den asynkrona åtgärd som representeras av operand har slutförts.

await_expression
    : 'await' unary_expression
    ;

En await_expression tillåts endast i en asynkron funktions brödtext (§15.15). Inom den närmaste omslutande asynkrona funktionen ska en await_expression inte ske på följande platser:

  • Inuti en kapslad (icke-asynkron) anonym funktion
  • Inuti blocket i en lock_statement
  • I en anonym funktionskonvertering till en uttrycksträdstyp (§10.7.3)
  • I ett osäkert sammanhang

Obs! En await_expression kan inte förekomma på de flesta platser inom en query_expression, eftersom de omvandlas syntaktiskt till att använda lambda-uttryck som inte är asynkrona. slutkommentar

I en asynkron funktion await ska inte användas som en available_identifier även om den ordagranna identifieraren @await kan användas. Det finns därför ingen syntaktisk tvetydighet mellan await_expressions och olika uttryck som omfattar identifierare. Utanför asynkrona funktioner await fungerar som en normal identifierare.

Operand för en await_expression kallas uppgiften. Den representerar en asynkron åtgärd som kanske eller kanske inte är slutförd vid den tidpunkt då await_expression utvärderas. Syftet med operatorn await är att avbryta körningen av den omslutande asynkrona funktionen tills den väntande uppgiften är klar och sedan få resultatet.

12.9.8.2 Väntande uttryck

Uppgiften för en await_expression måste vara väntande. Ett uttryck t är väntande om något av följande gäller:

  • t är av kompileringstidstyp dynamic
  • t har en tillgänglig instans eller tilläggsmetod som anropas GetAwaiter utan parametrar och inga typparametrar och en returtyp A som alla följande undantag för:
    • A implementerar gränssnittet System.Runtime.CompilerServices.INotifyCompletion (kallas därefter INotifyCompletion för korthet)
    • A har en tillgänglig, läsbar instansegenskap IsCompleted av typen bool
    • A har en tillgänglig instansmetod GetResult utan parametrar och inga typparametrar

Syftet med GetAwaiter metoden är att hämta en awaiter för uppgiften. Typen A kallas för awaiter-typen för await-uttrycket.

Syftet med egenskapen IsCompleted är att avgöra om uppgiften redan är slutförd. I så fall behöver du inte pausa utvärderingen.

Syftet med INotifyCompletion.OnCompleted metoden är att registrera en "fortsättning" för uppgiften, dvs. ett ombud (av typen System.Action) som anropas när uppgiften är klar.

Syftet med GetResult metoden är att få resultatet av uppgiften när den är klar. Det här resultatet kan slutföras, eventuellt med ett resultatvärde, eller så kan det vara ett undantag som genereras av GetResult metoden.

12.9.8.3 Klassificering av inväntningsuttryck

Uttrycket await t klassificeras på samma sätt som uttrycket (t).GetAwaiter().GetResult(). Om returtypen är GetResult voidklassificeras därför await_expression som ingenting. Om den har en typ som intevoid returneras klassificeras await_expression som ett värde av typen T.T

12.9.8.4 Körningsutvärdering av await-uttryck

Vid körning utvärderas uttrycket await t enligt följande:

  • En awaiter a hämtas genom att utvärdera uttrycket (t).GetAwaiter().
  • A boolb erhålls genom att utvärdera uttrycket (a).IsCompleted.
  • Om b är false beror utvärderingen på om a implementerar gränssnittet System.Runtime.CompilerServices.ICriticalNotifyCompletion (kallas därefter ICriticalNotifyCompletion för korthet). Den här kontrollen görs vid bindningstillfället. d.v.s. vid körning om a har kompileringstidstypen dynamic, och vid kompileringstillfället annars. Låt r oss ange återtagandedelegaten (§15.15):
    • Om a inte implementerar ICriticalNotifyCompletionutvärderas uttrycket ((a) as INotifyCompletion).OnCompleted(r) .
    • Om a implementerar ICriticalNotifyCompletionutvärderas uttrycket ((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r) .
    • Utvärderingen pausas sedan och kontrollen returneras till den aktuella anroparen för funktionen async.
  • Antingen omedelbart efter (om b var ), eller vid senare anrop av återtagandedelegaten (om b var false), utvärderas uttrycket (a).GetResult() true. Om det returnerar ett värde är det värdet resultatet av await_expression. Annars är resultatet ingenting.

En awaiter implementerar gränssnittsmetoderna INotifyCompletion.OnCompleted och ICriticalNotifyCompletion.UnsafeOnCompleted bör göra så att ombudet r anropas högst en gång. I annat fall är beteendet för den omslutande asynkrona funktionen odefinierat.

12.10 Aritmetiska operatorer

12.10.1 Allmänt

Operatorerna *, /, %, +och - kallas för aritmetiska operatorer.

multiplicative_expression
    : unary_expression
    | multiplicative_expression '*' unary_expression
    | multiplicative_expression '/' unary_expression
    | multiplicative_expression '%' unary_expression
    ;

additive_expression
    : multiplicative_expression
    | additive_expression '+' multiplicative_expression
    | additive_expression '-' multiplicative_expression
    ;

Om en operand av en aritmetikoperator har kompileringstidstypen dynamicär uttrycket dynamiskt bundet (§12.3.3). I det här fallet är dynamickompileringstidstypen för uttrycket , och den lösning som beskrivs nedan sker vid körning med hjälp av körningstypen för de operander som har kompileringstypen dynamic.

12.10.2 Multiplikationsoperator

För en åtgärd i formuläret x * ytillämpas binär operatoröverbelastningsmatchning (§12.4.5) för att välja en specifik operatorimplementering. Operanderna konverteras till parametertyperna för den valda operatorn och typen av resultat är operatorns returtyp.

De fördefinierade multiplikationsoperatorerna visas nedan. Operatorerna beräknar alla produkten av x och y.

  • Heltals multiplikation:

    int operator *(int x, int y);
    uint operator *(uint x, uint y);
    long operator *(long x, long y);
    ulong operator *(ulong x, ulong y);
    

    Om produkten i ett checked sammanhang ligger utanför resultattypens intervall genereras en System.OverflowException . I ett unchecked sammanhang rapporteras inte spill och några betydande högordningsbitar utanför resultattypens intervall ignoreras.

  • Multiplikation med flyttals:

    float operator *(float x, float y);
    double operator *(double x, double y);
    

    Produkten beräknas enligt reglerna i IEC 60559-aritmetik. I följande tabell visas resultatet av alla möjliga kombinationer av icke-ändliga värden, nollor, infiniteter och NaN. I tabellen x och y är positiva ändliga värden. z är resultatet av , avrundat x * ytill närmaste representerande värde. Om resultatets omfattning är för stor för måltypen z är oändligheten. På grund av avrundning z kan vara noll även om varken x eller y är noll.

    +y -y +0 -0 +∞ -∞ NaN
    +x +z -z +0 -0 +∞ -∞ NaN
    -x -z +z -0 +0 -∞ +∞ NaN
    +0 +0 -0 +0 -0 NaN NaN NaN
    -0 -0 +0 -0 +0 NaN NaN NaN
    +∞ +∞ -∞ NaN NaN +∞ -∞ NaN
    -∞ -∞ +∞ NaN NaN -∞ +∞ NaN
    NaN NaN NaN NaN NaN NaN NaN NaN

    (Om inget annat anges innebär användningen av "+" i flyttalstabellerna i §12.10.2§12.10.6 att värdet är positivt; användningen av "-" innebär att värdet är negativt, och avsaknaden av ett tecken innebär att värdet kan vara positivt eller negativt eller inte har något tecken (NaN).)

  • Decimal multiplikation:

    decimal operator *(decimal x, decimal y);
    

    Om storleken på det resulterande värdet är för stort för att representera i decimalformatet genereras en System.OverflowException . På grund av avrundning kan resultatet bli noll även om ingen av operanderna är noll. Resultatets skala, före avrundning, är summan av skalningarna för de två operanderna. Decimal multiplikation motsvarar att använda multiplikationsoperatorn av typen System.Decimal.

Lyfts (§12.4.8) bildar av de unlifted fördefinierade multiplikationsoperatorerna som definieras ovan, är också fördefinierade.

12.10.3 Divisionsoperatör

För en åtgärd i formuläret x / ytillämpas binär operatoröverbelastningsmatchning (§12.4.5) för att välja en specifik operatorimplementering. Operanderna konverteras till parametertyperna för den valda operatorn och typen av resultat är operatorns returtyp.

De fördefinierade divisionsoperatorerna visas nedan. Operatorerna beräknar alla kvoten x för och y.

  • Heltalsdivision:

    int operator /(int x, int y);
    uint operator /(uint x, uint y);
    long operator /(long x, long y);
    ulong operator /(ulong x, ulong y);
    

    Om värdet för rätt operand är noll genereras en System.DivideByZeroException .

    Divisionen avrundar resultatet mot noll. Således är det absoluta värdet för resultatet det största möjliga heltalet som är mindre än eller lika med det absoluta värdet för kvoten för de två operanderna. Resultatet är noll eller positivt när de två operanderna har samma tecken och noll eller negativa när de två operanderna har motsatta tecken.

    Om den vänstra operanden är den minsta representable int eller long värde och den högra operand är –1, uppstår ett spill. I ett checked sammanhang leder detta till att en System.ArithmeticException (eller en underklass därav) genereras. I ett unchecked sammanhang är det implementeringsdefinierat om huruvida en System.ArithmeticException (eller en underklass därav) genereras eller om spillet inte rapporteras med det resulterande värdet som den vänstra operandens.

  • Flyttalsdelning:

    float operator /(float x, float y);
    double operator /(double x, double y);
    

    Kvoten beräknas enligt reglerna i IEC 60559-aritmetik. I följande tabell visas resultatet av alla möjliga kombinationer av icke-ändliga värden, nollor, infiniteter och NaN. I tabellen x och y är positiva ändliga värden. z är resultatet av , avrundat x / ytill närmaste representerande värde.

    +y -y +0 -0 +∞ -∞ NaN
    +x +z -z +∞ -∞ +0 -0 NaN
    -x -z +z -∞ +∞ -0 +0 NaN
    +0 +0 -0 NaN NaN +0 -0 NaN
    -0 -0 +0 NaN NaN -0 +0 NaN
    +∞ +∞ -∞ +∞ -∞ NaN NaN NaN
    -∞ -∞ +∞ -∞ +∞ NaN NaN NaN
    NaN NaN NaN NaN NaN NaN NaN NaN
  • Decimaldelning:

    decimal operator /(decimal x, decimal y);
    

    Om värdet för rätt operand är noll genereras en System.DivideByZeroException . Om storleken på det resulterande värdet är för stort för att representera i decimalformatet genereras en System.OverflowException . På grund av avrundning kan resultatet bli noll även om den första operanden inte är noll. Resultatets skala, före avrundning, är den närmaste skalan till önskad skala som bevarar ett resultat som är lika med det exakta resultatet. Den önskade skalan är skalan för x mindre skalan för y.

    Decimaldelning motsvarar att använda divisionsoperatorn av typen System.Decimal.

Lyfts (§12.4.8) bildar av de unlifted fördefinierade uppdelningoperatorerna som definieras ovan, är också fördefinierade.

12.10.4 Restoperator

För en åtgärd i formuläret x % ytillämpas binär operatoröverbelastningsmatchning (§12.4.5) för att välja en specifik operatorimplementering. Operanderna konverteras till parametertyperna för den valda operatorn och typen av resultat är operatorns returtyp.

De fördefinierade restoperatorerna visas nedan. Operatorerna beräknar resten av divisionen mellan x och y.

  • Heltalsrester:

    int operator %(int x, int y);
    uint operator %(uint x, uint y);
    long operator %(long x, long y);
    ulong operator %(ulong x, ulong y);
    

    Resultatet av x % y är värdet som genereras av x – (x / y) * y. Om y är noll utlöses en System.DivideByZeroException .

    Om den vänstra operanden är det minsta int värdet eller long värdet och den högra operanden är –1, genereras en System.OverflowException om och endast om x / y skulle utlösa ett undantag.

  • Flyttalsrester:

    float operator %(float x, float y);
    double operator %(double x, double y);
    

    I följande tabell visas resultatet av alla möjliga kombinationer av icke-ändliga värden, nollor, infiniteter och NaN. I tabellen x och y är positiva ändliga värden. z är resultatet av x % y och beräknas som x – n * y, där n är det största möjliga heltalet som är mindre än eller lika med x / y. Den här metoden för att beräkna resten motsvarar den som används för heltalsoperor, men skiljer sig från IEC 60559-definitionen (där n är heltalet närmast x / y).

    +y -y +0 -0 +∞ -∞ NaN
    +x +z +z NaN NaN +x +x NaN
    -x -z -z NaN NaN -x -x NaN
    +0 +0 +0 NaN NaN +0 +0 NaN
    -0 -0 -0 NaN NaN -0 -0 NaN
    +∞ NaN NaN NaN NaN NaN NaN NaN
    -∞ NaN NaN NaN NaN NaN NaN NaN
    NaN NaN NaN NaN NaN NaN NaN NaN
  • Decimaler:

    decimal operator %(decimal x, decimal y);
    

    Om värdet för rätt operand är noll genereras en System.DivideByZeroException . Den är implementeringsdefinierad när en System.ArithmeticException (eller en underklass därav) genereras. Ett genomförande i enlighet med detta får inte utlösa ett undantag för x % y i vilket fall som helst när x / y ett undantag inte utlöser något undantag. Resultatets skala, före avrundning, är den större av skalningarna för de två operanderna, och tecknet för resultatet, om det inte är noll, är detsamma som för x.

    Decimaler motsvarar att använda restoperatorn av typen System.Decimal.

    Obs! Dessa regler säkerställer att resultatet för alla typer aldrig har motsatt tecken på den vänstra operanden. slutkommentar

Lyfts (§12.4.8) bildar av de unlifted fördefinierade restoperatorerna som definieras ovan, är också fördefinierade.

12.10.5 Additionsoperator

För en åtgärd i formuläret x + ytillämpas binär operatoröverbelastningsmatchning (§12.4.5) för att välja en specifik operatorimplementering. Operanderna konverteras till parametertyperna för den valda operatorn och typen av resultat är operatorns returtyp.

De fördefinierade tilläggsoperatorerna visas nedan. För numeriska typer och uppräkningstyper beräknar de fördefinierade additionsoperatorerna summan av de två operanderna. När en eller båda operanderna är av typen stringsammanfogar de fördefinierade tilläggsoperatorerna strängrepresentationen av operanderna.

  • Heltalstillägg:

    int operator +(int x, int y);
    uint operator +(uint x, uint y);
    long operator +(long x, long y);
    ulong operator +(ulong x, ulong y
    

    Om summan i ett checked sammanhang ligger utanför resultattypens intervall genereras en System.OverflowException . I ett unchecked sammanhang rapporteras inte spill och några betydande högordningsbitar utanför resultattypens intervall ignoreras.

  • Flyttalstillägg:

    float operator +(float x, float y);
    double operator +(double x, double y);
    

    Summan beräknas enligt reglerna i IEC 60559-aritmetik. I följande tabell visas resultatet av alla möjliga kombinationer av icke-ändliga värden, nollor, infiniteter och NaN. I tabellen x , och y är ickezero ändliga värden, och z är resultatet av x + y. Om x och y har samma storlek men motsatta tecken, z är positiv noll. Om x + y är för stort för att representera i måltypen är z en oändlighet med samma tecken som x + y.

    y +0 -0 +∞ -∞ NaN
    x z x x +∞ -∞ NaN
    +0 y +0 +0 +∞ –∞ NaN
    -0 y +0 -0 +∞ -∞ NaN
    +∞ +∞ +∞ +∞ +∞ NaN NaN
    -∞ -∞ -∞ -∞ NaN -∞ NaN
    NaN NaN NaN NaN NaN NaN NaN
  • Decimaltillägg:

    decimal operator +(decimal x, decimal y);
    

    Om storleken på det resulterande värdet är för stort för att representera i decimalformatet genereras en System.OverflowException . Resultatets skala, före avrundning, är den större av de två operandernas skalor.

    Decimaltillägg motsvarar att använda additionsoperatorn av typen System.Decimal.

  • Uppräkningstillägg. Varje uppräkningstyp tillhandahåller implicit följande fördefinierade operatorer, där E är uppräkningstypen, och U är den underliggande typen av E:

    E operator +(E x, U y);
    E operator +(U x, E y);
    

    Vid körning utvärderas dessa operatorer exakt som (E)((U)x + (U)y).

  • Strängsammanfogning:

    string operator +(string x, string y);
    string operator +(string x, object y);
    string operator +(object x, string y);
    

    Dessa överlagringar av binäroperatorn + utför strängsammanfogning. Om en operand av strängsammanfogning är nullersätts en tom sträng. Annars konverteras alla icke-operanderstring till dess strängrepresentation genom att anropa den virtuella ToString metoden som ärvts från typen object. Om ToString returnerar nullersätts en tom sträng.

    Exempel:

    class Test
    {
        static void Main()
        {
            string s = null;
            Console.WriteLine("s = >" + s + "<");  // Displays s = ><
    
            int i = 1;
            Console.WriteLine("i = " + i);         // Displays i = 1
    
            float f = 1.2300E+15F;
            Console.WriteLine("f = " + f);         // Displays f = 1.23E+15
    
            decimal d = 2.900m;
            Console.WriteLine("d = " + d);         // Displays d = 2.900
       }
    }
    

    Utdata som visas i kommentarerna är det typiska resultatet på ett amerikansk-engelskt system. Exakt utdata kan bero på de regionala inställningarna för körningsmiljön. Själva strängsammanfogningsoperatorn beter sig på samma sätt i varje enskilt fall, men de ToString metoder som implicit anropas under körningen kan påverkas av regionala inställningar.

    slutexempel

    Resultatet av strängsammanfogningsoperatorn är en string som består av tecknen i den vänstra operanden följt av tecknen i den högra operanden. Strängsammanfogningsoperatorn returnerar aldrig ett null värde. En System.OutOfMemoryException kan genereras om det inte finns tillräckligt med minne tillgängligt för att allokera den resulterande strängen.

  • Delegera kombination. Varje ombudstyp tillhandahåller implicit följande fördefinierade operator, där D är ombudstypen:

    D operator +(D x, D y);
    

    Om den första operanden är nullär resultatet av åtgärden värdet för den andra operanden (även om det också nullär ). Om den andra operanden annars är nullär resultatet av åtgärden värdet för den första operanden. Annars är resultatet av åtgärden en ny delegatinstans vars anropslista består av elementen i anropslistan för den första operanden, följt av elementen i anropslistan för den andra operanden. Den resulterande delegatens anropslista är alltså sammanlänkningen av anropslistorna för de två operanderna.

    Obs! Exempel på ombudskombinationer finns i §12.10.6 och §20.6. Eftersom System.Delegate inte är en ombudstyp definieras inte operatorn + för den. slutkommentar

Lyfts (§12.4.8) bildar av de unlifted fördefinierade tilläggoperatorerna som definieras ovan, är också fördefinierade.

12.10.6 Subtraktionsoperator

För en åtgärd i formuläret x – ytillämpas binär operatoröverbelastningsmatchning (§12.4.5) för att välja en specifik operatorimplementering. Operanderna konverteras till parametertyperna för den valda operatorn och typen av resultat är operatorns returtyp.

De fördefinierade subtraktionsoperatorerna visas nedan. Operatorerna subtraherar y alla från x.

  • Heltalsundertraktion:

    int operator –(int x, int y);
    uint operator –(uint x, uint y);
    long operator –(long x, long y);
    ulong operator –(ulong x, ulong y
    

    Om skillnaden i ett checked sammanhang ligger utanför resultattypens intervall genereras en System.OverflowException . I ett unchecked sammanhang rapporteras inte spill och några betydande högordningsbitar utanför resultattypens intervall ignoreras.

  • Flyttalsundertraktion:

    float operator –(float x, float y);
    double operator –(double x, double y);
    

    Skillnaden beräknas enligt reglerna för IEC 60559-aritmetik. I följande tabell visas resultatet av alla möjliga kombinationer av icke-ändliga värden, nollor, infiniteter och NaN. I tabellen x , och y är ickezero ändliga värden, och z är resultatet av x – y. Om x och y är lika med, z är positiv noll. Om x – y är för stort för att representera i måltypen är z en oändlighet med samma tecken som x – y.

    y +0 -0 +∞ -∞ NaN
    x z x x -∞ +∞ NaN
    +0 -y +0 +0 -∞ +∞ NaN
    -0 -y -0 +0 -∞ +∞ NaN
    +∞ +∞ +∞ +∞ NaN +∞ NaN
    -∞ -∞ -∞ -∞ -∞ NaN NaN
    NaN NaN NaN NaN NaN NaN NaN

    (I tabellen ovan anger posterna -y negationen av y, inte att värdet är negativt.)

  • Decimal subtraktion:

    decimal operator –(decimal x, decimal y);
    

    Om storleken på det resulterande värdet är för stort för att representera i decimalformatet genereras en System.OverflowException . Resultatets skala, före avrundning, är den större av de två operandernas skalor.

    Decimal subtraktion motsvarar att använda subtraktionsoperatorn av typen System.Decimal.

  • Uppräkningsundertraktion. Varje uppräkningstyp tillhandahåller implicit följande fördefinierade operator, där E är uppräkningstypen, och U är den underliggande typen av E:

    U operator –(E x, E y);
    

    Den här operatorn utvärderas exakt som (U)((U)x – (U)y). Med andra ord beräknar operatorn skillnaden mellan ordningsvärdena x för och y, och typen av resultat är den underliggande typen av uppräkning.

    E operator –(E x, U y);
    

    Den här operatorn utvärderas exakt som (E)((U)x – y). Med andra ord subtraherar operatorn ett värde från den underliggande typen av uppräkning, vilket ger ett värde för uppräkningen.

  • Delegera borttagning. Varje ombudstyp tillhandahåller implicit följande fördefinierade operator, där D är ombudstypen:

    D operator –(D x, D y);
    

    Semantiken är följande:

    • Om den första operanden är nullär nullresultatet av åtgärden .
    • Om den andra operanden annars är nullär resultatet av åtgärden värdet för den första operanden.
    • Annars representerar båda operanderna icke-tomma anropslistor (§20.2).
      • Om listorna jämför lika, enligt vad som bestäms av ombudets likhetsoperator (§12.12.9), blir nullresultatet av åtgärden .
      • Annars är resultatet av åtgärden en ny anropslista som består av den första operandens lista med den andra operandens poster borttagna från den, förutsatt att den andra operandens lista är en underlista för den första. (För att fastställa likhet mellan underlistor jämförs motsvarande poster som för operatorn för ombudsjämlikhet.) Om den andra operandens lista matchar flera underlistor med sammanhängande poster i den första operandens lista tas den sista matchande underlistan över angränsande poster bort.
      • Annars är resultatet av åtgärden värdet för den vänstra operanden.

    Ingen av operandernas listor (om några) ändras i processen.

    Exempel:

    delegate void D(int x);
    
    class C
    {
        public static void M1(int i) { ... }
        public static void M2(int i) { ... }
    }
    
    class Test
    {
        static void Main()
        {
            D cd1 = new D(C.M1);
            D cd2 = new D(C.M2);
            D list = null;
    
            list = null - cd1;                             // null
            list = (cd1 + cd2 + cd2 + cd1) - null;         // M1 + M2 + M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - cd1;          // M1 + M2 + M2
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2);  // M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd2);  // M1 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd1);  // M1 + M2
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd1);  // M1 + M2 + M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2 + cd2 + cd1);  // null
        }
    }
    

    slutexempel

Lyfts (§12.4.8) bildar av de unlifted fördefinierade subtraktionsoperatorerna som definieras ovan, är också fördefinierade.

12.11 Skiftoperatorer

Operatorerna << och >> används för att utföra bitväxlingsåtgärder.

shift_expression
    : additive_expression
    | shift_expression '<<' additive_expression
    | shift_expression right_shift additive_expression
    ;

Om en operand av en shift_expression har kompileringstidstypen dynamicär uttrycket dynamiskt bundet (§12.3.3). I det här fallet är dynamickompileringstidstypen för uttrycket , och den lösning som beskrivs nedan sker vid körning med hjälp av körningstypen för de operander som har kompileringstypen dynamic.

För en åtgärd i formuläret x << count eller x >> counttillämpas binär operatoröverbelastningsmatchning (§12.4.5) för att välja en specifik operatorimplementering. Operanderna konverteras till parametertyperna för den valda operatorn och typen av resultat är operatorns returtyp.

När en överlagrad skiftoperator deklareras ska den första operandens typ alltid vara den klass eller struct som innehåller operatordeklarationen, och typen av den andra operanden ska alltid vara int.

De fördefinierade skiftoperatorerna visas nedan.

  • Skift vänster:

    int operator <<(int x, int count);
    uint operator <<(uint x, int count);
    long operator <<(long x, int count);
    ulong operator <<(ulong x, int count);
    

    Operatorn << skiftar x åt vänster av ett antal bitar som beräknas enligt beskrivningen nedan.

    Högordningsbitarna utanför resultattypens x intervall ignoreras, de återstående bitarna flyttas åt vänster och de tomma bitpositionerna i låg ordning är inställda på noll.

  • Skift höger:

    int operator >>(int x, int count);
    uint operator >>(uint x, int count);
    long operator >>(long x, int count);
    ulong operator >>(ulong x, int count);
    

    Operatorn >> skiftar x åt höger efter ett antal bitar som beräknas enligt beskrivningen nedan.

    När x är av typen int eller longignoreras x bitarna med låg ordning, de återstående bitarna flyttas åt höger och de tomma bitpositionerna i hög ordning är inställda på noll om x de inte är negativa och ställs in på ett om x det är negativt.

    När x är av typen uint eller ulongignoreras de låga bitarna i x , de återstående bitarna flyttas åt höger och de tomma bitpositionerna i hög ordning är inställda på noll.

För de fördefinierade operatorerna beräknas antalet bitar som ska flyttas enligt följande:

  • När typen av x är int eller uintanges skiftantalet av fem bitar med låg ordning i count. Med andra ord beräknas skiftantalet från count & 0x1F.
  • När typen av x är long eller ulonganges skiftantalet av den låga ordningen sex bitar av count. Med andra ord beräknas skiftantalet från count & 0x3F.

Om det resulterande skiftantalet är noll returnerar skiftoperatorerna helt enkelt värdet xför .

Skiftåtgärder orsakar aldrig spill och ger samma resultat i markerade och avmarkerade kontexter.

När operatorns >> vänstra operande är av en signerad integraltyp utför operatorn ett aritmetiskt skift till höger där värdet för den viktigaste biten (teckenbiten) för operanden sprids till de tomma bitpositionerna i hög ordning. När operatorns >> vänstra operande är av en osignerad integraltyp utför operatorn ett logiskt skift höger där tomma bitpositioner i hög ordning alltid är inställda på noll. För att utföra den motsatta åtgärden som härleds från operandtypen kan explicita avgjutningar användas.

Exempel: Om x är en variabel av typen intutför åtgärden unchecked ((int)((uint)x >> y)) en logisk skifträtt till höger om x. slutexempel

Lyfts (§12.4.8) bildar av de unlifted fördefinierade skiftoperatorerna som definieras ovan, är också fördefinierade.

12.12 Relations- och typtestningsoperatorer

12.12.1 Allmänt

Operatorerna ==, !=, <, >, >=<=, isoch as kallas för relations- och typtestningsoperatorerna.

relational_expression
    : shift_expression
    | relational_expression '<' shift_expression
    | relational_expression '>' shift_expression
    | relational_expression '<=' shift_expression
    | relational_expression '>=' shift_expression
    | relational_expression 'is' type
    | relational_expression 'is' pattern
    | relational_expression 'as' type
    ;

equality_expression
    : relational_expression
    | equality_expression '==' relational_expression
    | equality_expression '!=' relational_expression
    ;

Obs! Sökning efter rätt operand för operatorn is måste först testas som en typ och sedan som ett uttryck som kan sträcka sig över flera token. Om operanden är en expreesion måste mönsteruttrycket ha företräde minst så högt som shift_expression. slutkommentar

Operatören beskrivs i §12.12.12 och operatören as beskrivs i §12.12.13.is

Operatorerna ==, !=, <, <= >och >= är jämförelseoperatorer.

Om en default_literal (§12.8.21) används som operand för en <, >, <=eller >= operator uppstår ett kompileringsfel. Om en default_literal används som båda operanderna för en == eller != -operatorn uppstår ett kompileringsfel. Om en default_literal används som vänster operand för is operatorn eller as uppstår ett kompileringsfel.

Om en operand av en jämförelseoperator har kompileringstidstypen dynamicär uttrycket dynamiskt bundet (§12.3.3). I det här fallet är dynamickompileringstidstypen för uttrycket , och den lösning som beskrivs nedan sker vid körning med hjälp av körningstypen för de operander som har kompileringstidstypen dynamic.

För en åtgärd i formuläret x «op» y, där «op» är en jämförelseoperator, tillämpas överbelastningsmatchning (§12.4.5) för att välja en specifik operatorimplementering. Operanderna konverteras till parametertyperna för den valda operatorn och typen av resultat är operatorns returtyp. Om båda operanderna i en equality_expression är literalen null utförs inte överlagringsupplösningen och uttrycket utvärderas till ett konstant värde av true eller false beroende på om operatorn är == eller !=.

De fördefinierade jämförelseoperatorerna beskrivs i följande underklienter. Alla fördefinierade jämförelseoperatorer returnerar ett resultat av typen bool, enligt beskrivningen i följande tabell.

Åtgärd Resultat
x == y true om x är lika med y, false annars
x != y true om x inte är lika med y, false annars
x < y true om x är mindre än y, false annars
x > y true om x är större än y, false annars
x <= y true om x är mindre än eller lika med y, false annars
x >= y true om x är större än eller lika med y, false annars

12.12.2 Jämförelseoperatorer för heltal

De fördefinierade heltalsjämförelseoperatorerna är:

bool operator ==(int x, int y);
bool operator ==(uint x, uint y);
bool operator ==(long x, long y);
bool operator ==(ulong x, ulong y);

bool operator !=(int x, int y);
bool operator !=(uint x, uint y);
bool operator !=(long x, long y);
bool operator !=(ulong x, ulong y);

bool operator <(int x, int y);
bool operator <(uint x, uint y);
bool operator <(long x, long y);
bool operator <(ulong x, ulong y);

bool operator >(int x, int y);
bool operator >(uint x, uint y);
bool operator >(long x, long y);
bool operator >(ulong x, ulong y);

bool operator <=(int x, int y);
bool operator <=(uint x, uint y);
bool operator <=(long x, long y);
bool operator <=(ulong x, ulong y);

bool operator >=(int x, int y);
bool operator >=(uint x, uint y);
bool operator >=(long x, long y);
bool operator >=(ulong x, ulong y);

Var och en av dessa operatorer jämför de numeriska värdena för de två heltalsopernderna och returnerar ett bool värde som anger om den specifika relationen är true eller false.

Lyfts (§12.4.8) bildar av de unlifted fördefinierade heltaljämförelseoperatorerna som definieras ovan, är också fördefinierade.

12.12.3 Jämförelseoperatorer för flyttals

De fördefinierade flyttalsjämförelseoperatorerna är:

bool operator ==(float x, float y);
bool operator ==(double x, double y);

bool operator !=(float x, float y);
bool operator !=(double x, double y);

bool operator <(float x, float y);
bool operator <(double x, double y);

bool operator >(float x, float y);
bool operator >(double x, double y);

bool operator <=(float x, float y);
bool operator <=(double x, double y);

bool operator >=(float x, float y);
bool operator >=(double x, double y);

Operatörerna jämför operanderna enligt reglerna i IEC 60559-standarden:

Om någon av operanderna är NaN är false resultatet för alla operatorer utom !=, för vilka resultatet är true. För två operander x != y ger alltid samma resultat som !(x == y). Men när en eller båda operanderna är NaN, <ger operatorerna , >, <=och inte >= samma resultat som den logiska negationen för den motsatta operatorn.

Exempel: Om något av x och y är NaN är falsedet x < y , men !(x >= y) är true. slutexempel

När ingen av operanderna är NaN jämför operatorerna värdena för de två flyttalsopernderna med avseende på ordningen

–∞ < –max < ... < –min < –0.0 == +0.0 < +min < ... < +max < +∞

där min och max är de minsta och största positiva finita värdena som kan representeras i det angivna flyttalsformatet. Viktiga effekter av den här ordningen är:

  • Negativa och positiva nollor anses vara lika.
  • En negativ oändlighet anses vara mindre än alla andra värden, men lika med en annan negativ oändlighet.
  • En positiv oändlighet anses vara större än alla andra värden, men lika med en annan positiv oändlighet.

Lyfts (§12.4.8) bildar av de unlifted fördefinierade flyttalsjämförelseoperatorerna som definieras ovan, är också fördefinierade.

12.12.4 Decimal jämförelseoperatorer

De fördefinierade decimaljämförelseoperatorerna är:

bool operator ==(decimal x, decimal y);
bool operator !=(decimal x, decimal y);
bool operator <(decimal x, decimal y);
bool operator >(decimal x, decimal y);
bool operator <=(decimal x, decimal y);
bool operator >=(decimal x, decimal y);

Var och en av dessa operatorer jämför de numeriska värdena för de två decimalopernderna och returnerar ett bool värde som anger om den specifika relationen är true eller false. Varje decimaljämförelse motsvarar att använda motsvarande relations- eller likhetsoperator av typen System.Decimal.

Lyfts (§12.4.8) bildar av de unlifted fördefinierade decimaljämförelseoperatorerna som definieras ovan, är också fördefinierade.

12.12.5 Booleska likhetsoperatorer

De fördefinierade booleska likhetsoperatorerna är:

bool operator ==(bool x, bool y);
bool operator !=(bool x, bool y);

Resultatet av == är true om både x och y är true eller om både x och y är false. Annars blir falseresultatet .

Resultatet av != är false om både x och y är true eller om både x och y är false. Annars blir trueresultatet . När operanderna är av typen boolgenererar operatorn != samma resultat som operatorn ^ .

Lyfts (§12.4.8) bildar av de unlifted fördefinierade booleska likhetoperatorerna som definieras ovan, är också fördefinierade.

12.12.6 Uppräkningsjämförelseoperatorer

Varje uppräkningstyp tillhandahåller implicit följande fördefinierade jämförelseoperatorer

bool operator ==(E x, E y);
bool operator !=(E x, E y);

bool operator <(E x, E y);
bool operator >(E x, E y);
bool operator <=(E x, E y);
bool operator >=(E x, E y);

Resultatet av utvärderingen x «op» y, där x och y är uttryck av en uppräkningstyp E med en underliggande typ U, och «op» är en av jämförelseoperatorerna, är exakt samma som att ((U)x) «op» ((U)y)utvärdera . Med andra ord jämför jämförelseoperatorerna för uppräkningstypen helt enkelt de underliggande integralvärdena för de två operanderna.

Lyfts (§12.4.8) bildar av de unlifted fördefinierade uppräkningjämförelseoperatorerna som definieras ovan, är också fördefinierade.

12.12.7 Likhetsoperatorer för referenstyp

Varje klasstyp C tillhandahåller implicit följande fördefinierade likhetsoperatorer för referenstyper:

bool operator ==(C x, C y);
bool operator !=(C x, C y);

om inte fördefinierade likhetsoperatorer annars finns för C (till exempel när C är string eller System.Delegate).

Operatorerna returnerar resultatet av att jämföra de två referenserna för likhet eller icke-likhet. operator == returnerar true om och endast om x och y refererar till samma instans eller är båda null, medan operator != returnerar true om och endast om operator == med samma operander skulle returnera false.

Förutom normala tillämplighetsregler (§12.6.4.2) kräver de fördefinierade likhetsoperatorerna av referenstyp något av följande för att vara tillämpligt:

  • Båda operanderna är ett värde av en typ som är känd för att vara en reference_type eller literalen null. Dessutom finns det en identitet eller explicit referenskonvertering (§10.3.5) från antingen operande till den andra operandens typ.
  • Den ena operanden är literalen null, och den andra operanden är ett värde av typen T där T är en type_parameter som inte är känd för att vara en värdetyp och som inte har begränsningen för värdetyp.
    • Om vid körningen T är en värdetyp som inte kan nollas är false resultatet och == resultatet av != är true.
    • Om vid körning T är en nullbar värdetyp beräknas resultatet från HasValue operandens egenskap, enligt beskrivningen i (§12.12.10).
    • Om vid körning T är en referenstyp blir true resultatet om operand är null, och false annars.

Om inget av dessa villkor är sant uppstår ett bindningstidsfel.

Obs! Viktiga konsekvenser av dessa regler är:

  • Det är ett bindningstidsfel att använda de fördefinierade likhetsoperatorerna för referenstyper för att jämföra två referenser som är kända för att vara olika vid bindningstid. Om till exempel bindningstidstyperna för operanderna är två klasstyper, och om ingen av dem härleds från den andra, skulle det vara omöjligt för de två operanderna att referera till samma objekt. Åtgärden anses därför vara ett bindningstidsfel.
  • De fördefinierade operatorerna för referenstypjämlikhet tillåter inte att värdetypoperatorer jämförs (förutom när typparametrar jämförs med null, som hanteras särskilt).
  • Operander av fördefinierade likhetsoperatorer för referenstyper boxas aldrig. Det skulle vara meningslöst att utföra sådana boxningsåtgärder, eftersom referenser till de nyligen allokerade boxade instanserna nödvändigtvis skulle skilja sig från alla andra referenser.

För en åtgärd av formuläret x == y eller x != y, om det finns någon tillämplig användardefinierad operator == eller operator != finns, väljer operatorns regler för överbelastningsmatchning (§12.4.5) den operatorn i stället för den fördefinierade likhetsoperatorn för referenstyp. Det är alltid möjligt att välja den fördefinierade referenstypjämlikhetsoperatorn genom att uttryckligen gjuta en eller båda operanderna för att skriva object.

slutkommentar

Exempel: I följande exempel kontrolleras om ett argument av en icke-tränad typparametertyp är null.

class C<T>
{
   void F(T x)
   {
      if (x == null)
      {
          throw new ArgumentNullException();
      }
      ...
   }
}

Konstruktionen x == null tillåts även om T den kan representera en värdetyp som inte kan nollställas, och resultatet definieras helt enkelt som false när T är en värdetyp som inte kan nulleras.

slutexempel

För en åtgärd av formuläret x == y eller x != y, om någon tillämplig operator == eller operator != finns, väljer operatörens överbelastningsmatchning (§12.4.5) den operatorn i stället för den fördefinierade referenstypjämlikhetsoperatorn.

Obs! Det är alltid möjligt att välja den fördefinierade referenstypsjämlikhetsoperatorn genom att uttryckligen gjuta båda operanderna för att skriva object. slutkommentar

Exempel: Exemplet

class Test
{
    static void Main()
    {
        string s = "Test";
        string t = string.Copy(s);
        Console.WriteLine(s == t);
        Console.WriteLine((object)s == t);
        Console.WriteLine(s == (object)t);
        Console.WriteLine((object)s == (object)t);
    }
}

genererar utdata

True
False
False
False

Variablerna s och t refererar till två distinkta stränginstanser som innehåller samma tecken. Den första jämförelsen utdata eftersom den fördefinierade strängjämlikhetsoperatorn True (§12.12.8) väljs när båda operanderna är av typen string. Återstående jämförelser av alla utdata False eftersom överbelastningen string av operator == i typen inte är tillämplig när någon av operanderna har en bindningstidstyp av object.

Observera att ovanstående teknik inte är meningsfull för värdetyper. Exemplet

class Test
{
    static void Main()
    {
        int i = 123;
        int j = 123;
        Console.WriteLine((object)i == (object)j);
    }
}

utdata False eftersom avgjutningarna skapar referenser till två separata instanser av boxade int värden.

slutexempel

12.12.8 Operatorer för strängjämlikhet

De fördefinierade operatorerna för strängjämlikhet är:

bool operator ==(string x, string y);
bool operator !=(string x, string y);

Två string värden anses vara lika med när något av följande är sant:

  • Båda värdena är null.
  • Båda värdena är icke-referensernull till stränginstanser som har identiska längder och identiska tecken i varje teckenposition.

Operatorerna för strängjämlikhet jämför strängvärden i stället för strängreferenser. När två separata stränginstanser innehåller exakt samma teckensekvens är värdena för strängarna lika, men referenserna är olika.

Obs! Enligt beskrivningen i §12.12.7 kan likhetsoperatorerna för referenstyp användas för att jämföra strängreferenser i stället för strängvärden. slutkommentar

12.12.9 Delegera likhetsoperatorer

De fördefinierade operatorerna för ombudsjämlikhet är:

bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y);

Två delegatinstanser anses vara lika med följande:

  • Om någon av de delegerade instanserna är nullär de lika om och endast om båda är null.
  • Om ombuden har olika körningstyp är de aldrig lika.
  • Om båda de delegerade instanserna har en anropslista (§20.2) är dessa instanser lika om och endast om deras anropslistor är lika långa, och varje post i en anropslista är lika (enligt definitionen nedan) med motsvarande post, i ordning, i den andras anropslista.

Följande regler styr likheten mellan anropslistposter:

  • Om två anropslistposter båda refererar till samma statiska metod är posterna lika.
  • Om två anropslistposter båda refererar till samma icke-statiska metod på samma målobjekt (som definieras av referensparalikhetsoperatorerna) är posterna lika.
  • Anropslista poster som tagits fram från utvärderingen av semantiskt identiska anonyma funktioner (§12.19) med samma (eventuellt tomma) uppsättning av insamlade yttre variabelinstanser tillåts (men krävs inte) vara lika.

Om operatörens överbelastningsmatchning matchar antingen delegera likhetsoperatorn och bindningstidstyperna för båda operanderna är ombudstyper enligt beskrivningen i §20 i stället System.Delegateför , och det inte finns någon identitetskonvertering mellan operandtyperna av bindningstyp, uppstår ett bindningsfel.

Obs! Den här regeln förhindrar jämförelser som aldrig kan betrakta icke-värdennull som lika på grund av referenser till instanser av olika typer av ombud. slutkommentar

12.12.10 Likhetsoperatorer mellan nullbara värdetyper och null-literalen

Operatorerna == och != tillåter att den ena operanden är ett värde av en nullbar värdetyp och den andra är literalen null , även om det inte finns någon fördefinierad eller användardefinierad operator (i obelyftad eller hävd form) för åtgärden.

För en åtgärd av ett av formulären

x == null    null == x    x != null    null != x

där x är ett uttryck av en nullbar värdetyp, om operatorns överlagringsmatchning (§12.4.5) inte kan hitta en tillämplig operator, beräknas resultatet i stället från HasValue egenskapen x. Mer specifikt översätts de två första formulären till !x.HasValue, och de två sista formulären översätts till x.HasValue.

12.12.11 Tuppelns likhetsoperatorer

Tuppelns likhetsoperatorer tillämpas parvis på elementen i tuppelns operander i lexikal ordning.

Om varje operande x och y en eller != en == operator klassificeras antingen som en tuppeln eller som ett värde med tuppelns typ (§8.3.11), är operatorn en tuppelns likhetsoperator.

Om en operande e klassificeras som en tupplar ska elementen e1...en vara resultatet av utvärderingen av elementuttrycken i tuppelns uttryck. Om e inte är ett värde av tuppelns typ ska elementen vara t.Item1...t.Itemn där t är resultatet av utvärderingen e.

Operanderna och y en tupplars likhetsoperator x ska ha samma araitet, eller så uppstår ett kompileringstidsfel. För varje elementpar xi och yiska samma likhetsoperator tillämpas och ge ett resultat av typen bool, dynamic, en typ som har en implicit konvertering till booleller en typ som definierar operatorerna true och false .

Operatorn x == y för tuppelns likhet utvärderas på följande sätt:

  • Den vänstra operanden x utvärderas.
  • Operand y på höger sida utvärderas.
  • För varje elementpar xi och yi i lexikal ordning:
    • Operatorn xi == yi utvärderas och ett resultat av typen bool erhålls på följande sätt:
      • Om jämförelsen gav ett bool så är det resultatet.
      • Annars om jämförelsen gav en dynamic anropas operatorn false dynamiskt på den, och det resulterande bool värdet negeras med den logiska negationsoperatorn (!).
      • Om jämförelsetypen annars har en implicit konvertering till booltillämpas konverteringen.
      • Om jämförelsetypen annars har en operator falseanropas operatorn och det resulterande bool värdet negeras med den logiska negationsoperatorn (!).
    • Om resultatet bool är false, sker ingen ytterligare utvärdering och resultatet av tuppelns likhetsoperator är false.
  • Om alla elementjämförelser ger trueär resultatet av tuppelns likhetsoperator true.

Operatorn x != y för tuppelns likhet utvärderas på följande sätt:

  • Den vänstra operanden x utvärderas.
  • Operand y på höger sida utvärderas.
  • För varje elementpar xi och yi i lexikal ordning:
    • Operatorn xi != yi utvärderas och ett resultat av typen bool erhålls på följande sätt:
      • Om jämförelsen gav ett bool så är det resultatet.
      • Om jämförelsen annars gav en dynamic anropas operatorn true dynamiskt på den, och det resulterande bool värdet är resultatet.
      • Om jämförelsetypen annars har en implicit konvertering till booltillämpas konverteringen.
      • Om jämförelsetypen annars har en operator trueanropas operatorn och det resulterande bool värdet är resultatet.
    • Om resultatet bool är true, sker ingen ytterligare utvärdering och resultatet av tuppelns likhetsoperator är true.
  • Om alla elementjämförelser ger falseär resultatet av tuppelns likhetsoperator false.

12.12.12 Is-operatorn

Det finns två former av operatorn is . Den ena är operatorn is-type, som har en typ till höger. Den andra är is-pattern-operatorn, som har ett mönster till höger.

12.12.12.1 Operatorn är av typen

Operatorn is-type används för att kontrollera om körningstypen för ett objekt är kompatibel med en viss typ. Kontrollen utförs vid körning. Resultatet av åtgärden E is T, där E är ett uttryck och T är en annan typ än dynamic, är ett booleskt värde som anger om E är icke-null och kan konverteras till typ T av en referenskonvertering, en boxningskonvertering, en konvertering som inte är null, en omslutningskonvertering eller en omskrivningskonvertering.

Åtgärden utvärderas på följande sätt:

  1. Om E är en anonym funktion eller metodgrupp uppstår ett kompileringsfel
  2. Om E är literalen null , eller om värdet E för är null, är falseresultatet .
  3. Annars:
  4. Låt vara R körningstypen E.
  5. Låt D oss härledas från R följande:
  6. Om R är en nullbar värdetyp D är den underliggande typen av R.
  7. Annars D är R.
  8. Resultatet beror på D och T på följande sätt:
  9. Om T är en referenstyp blir true resultatet om:
    • det finns en identitetskonvertering mellan D och T,
    • D är en referenstyp och en implicit referenskonvertering från D till T finns, eller
    • Antingen: D är en värdetyp och en boxningskonvertering från D till T finns.
      Eller: D är en värdetyp och T är en gränssnittstyp som implementeras av D.
  10. Om T är en nullbar värdetyp blir true resultatet om D är den underliggande typen av T.
  11. Om T är en värdetyp som inte kan null-värdet är true resultatet om D och T är av samma typ.
  12. Annars blir falseresultatet .

Användardefinierade konverteringar beaktas inte av operatorn is .

Obs! Eftersom operatorn is utvärderas vid körning har alla typargument ersatts och det finns inga öppna typer (§8.4.3) att överväga. slutkommentar

Obs! Operatorn is kan förstås i termer av kompileringstidstyper och konverteringar på följande sätt, där C är kompileringstidstypen Eför :

  • Om kompileringstidstypen e är samma som T, eller om en implicit referenskonvertering (§10.2.8), boxningskonvertering (§10.2.9), omslutningskonvertering (§10.6) eller en explicit omskrivningskonvertering (§10.6) finns från kompileringstidstypen E till T:
    • Om C är av en värdetyp som inte kan nullvärde är trueresultatet av åtgärden .
    • Annars motsvarar resultatet av åtgärden att utvärdera E != null.
  • Om en explicit referenskonvertering (§10.3.5) eller omboxningskonvertering (§10.3.7) finns från C till T, eller om C eller T är en öppen typ (§8.4.3), ska körningskontroller enligt ovan vara peformerade.
  • Annars är ingen referens, boxning, omslutning eller omskrivningskonvertering av E typen T möjlig, och resultatet av åtgärden är false. En kompilator kan implementera optimeringar baserat på kompileringstidstypen.

slutkommentar

12.12.12.2 Is-pattern-operatorn

Is-pattern-operatorn används för att kontrollera om värdet som beräknas av ett uttryck matchar ett givet mönster (§11). Kontrollen utförs vid körning. Resultatet av is-pattern-operatorn är sant om värdet matchar mönstret. annars är det falskt.

För ett uttryck i formuläret E is P, där E är ett relationsuttryck av typen T och P är ett mönster, är det ett kompileringsfel om något av följande undantag:

  • E anger inget värde eller har ingen typ.
  • Mönstret P är inte tillämpligt (§11.2) på typen T.

12.12.13 As-operatorn

Operatorn as används för att explicit konvertera ett värde till en viss referenstyp eller nullbar värdetyp. Till skillnad från ett cast-uttryck (§12.9.7) genererar operatorn as aldrig ett undantag. Om den angivna konverteringen inte är möjlig är nulldet resulterande värdet i stället .

I en åtgärd i formuläret E as TE ska vara ett uttryck och T vara en referenstyp, en typparameter som är känd för att vara en referenstyp eller en nullbar värdetyp. Dessutom ska minst något av följande vara sant, eller på annat sätt uppstår ett kompileringsfel:

  • En identitet (§10.2.2), implicit nullable (§10.2.6), implicit referens (§10.2.8), boxning (§10.2.9), explicit nullable (§10.3.4), explicit referens (§10.3.5) eller wrapping (§8.3.12) konvertering finns från E till T.
  • Typen av E eller T är en öppen typ.
  • E är literalen null .

Om kompileringstidstypen E inte dynamicär genererar åtgärden E as T samma resultat som

E is T ? (T)(E) : (T)null

förutom att E endast utvärderas en gång. Kompilatorn kan förväntas optimera E as T för att utföra högst en körningstypkontroll i motsats till de två körningstypkontrollerna som antyds i expansionen ovan.

Om kompileringstidstypen E är dynamic, till skillnad från gjutningsoperatorn, är operatorn as inte dynamiskt bunden (§12.3.3). Därför är expansionen i det här fallet:

E is T ? (T)(object)(E) : (T)null

Observera att vissa konverteringar, till exempel användardefinierade konverteringar, inte är möjliga med operatorn as och i stället bör utföras med hjälp av cast-uttryck.

Exempel: I exemplet

class X
{
    public string F(object o)
    {
        return o as string;  // OK, string is a reference type
    }

    public T G<T>(object o)
        where T : Attribute
    {
        return o as T;       // Ok, T has a class constraint
    }

    public U H<U>(object o)
    {
        return o as U;       // Error, U is unconstrained
    }
}

typparametern T G för är känd för att vara en referenstyp, eftersom den har klassbegränsningen. Typparametern U H för är dock inte tillåten. Därför är det inte tillåtet att använda operatorn as i H .

slutexempel

12.13 Logiska operatorer

12.13.1 Allmänt

Operatorerna &, ^, och | kallas logiska operatorer.

and_expression
    : equality_expression
    | and_expression '&' equality_expression
    ;

exclusive_or_expression
    : and_expression
    | exclusive_or_expression '^' and_expression
    ;

inclusive_or_expression
    : exclusive_or_expression
    | inclusive_or_expression '|' exclusive_or_expression
    ;

Om en operand av en logisk operator har kompileringstidstypen dynamicär uttrycket dynamiskt bundet (§12.3.3). I det här fallet är dynamickompileringstidstypen för uttrycket , och den lösning som beskrivs nedan sker vid körning med hjälp av körningstypen för de operander som har kompileringstidstypen dynamic.

För en åtgärd i formuläret x «op» y, där «op» är en av de logiska operatorerna, tillämpas överbelastningsmatchning (§12.4.5) för att välja en specifik operatorimplementering. Operanderna konverteras till parametertyperna för den valda operatorn och typen av resultat är operatorns returtyp.

De fördefinierade logiska operatorerna beskrivs i följande underklienter.

12.13.2 Heltals logiska operatorer

De fördefinierade logiska heltalsoperatorerna är:

int operator &(int x, int y);
uint operator &(uint x, uint y);
long operator &(long x, long y);
ulong operator &(ulong x, ulong y);

int operator |(int x, int y);
uint operator |(uint x, uint y);
long operator |(long x, long y);
ulong operator |(ulong x, ulong y);

int operator ^(int x, int y);
uint operator ^(uint x, uint y);
long operator ^(long x, long y);
ulong operator ^(ulong x, ulong y);

Operatorn & beräknar det bitvis logiska OCH för de två operanderna, operatorn | beräknar den bitvis logiska OR:t för de två operanderna och ^ operatorn beräknar den bitvis logiska exklusiva OR för de två operanderna. Inga spill är möjliga från dessa åtgärder.

Hissade (§12.4.8) former av de olyftade fördefinierade heltals logiska operatorerna som definieras ovan är också fördefinierade.

12.13.3 Logiska uppräkningsoperatorer

Varje uppräkningstyp E tillhandahåller implicit följande fördefinierade logiska operatorer:

E operator &(E x, E y);
E operator |(E x, E y);
E operator ^(E x, E y);

Resultatet av utvärderingen x «op» y, där x och y är uttryck av en uppräkningstyp E med en underliggande typ U, och «op» är en av de logiska operatorerna, är exakt samma som att (E)((U)x «op» (U)y)utvärdera . Med andra ord utför uppräkningstypen logiska operatorer helt enkelt den logiska åtgärden på den underliggande typen av de två operanderna.

Lyfts (§12.4.8) bildar av de unlifted fördefinierade uppräkning logiska operatorerna som definieras ovan, är också fördefinierade.

12.13.4 Booleska logiska operatorer

De fördefinierade booleska logiska operatorerna är:

bool operator &(bool x, bool y);
bool operator |(bool x, bool y);
bool operator ^(bool x, bool y);

Resultatet av x & y är true om både x och y är true. Annars blir falseresultatet .

Resultatet av x | y är true om antingen x eller y är true. Annars blir falseresultatet .

Resultatet av är om är och är , eller x är false och y är true.falsetrue y x true x ^ y Annars blir falseresultatet . När operanderna är av typen boolberäknar operatorn ^ samma resultat som operatorn != .

12.13.5 Nullbar boolesk och | Operatörer

Den booleska typen bool? som kan vara null kan representera tre värden, true, falseoch null.

Precis som med de andra binära operatorerna är även de upplyfta formerna av de logiska operatorerna & och | (§12.13.4) fördefinierade:

bool? operator &(bool? x, bool? y);
bool? operator |(bool? x, bool? y);

Semantiken för de lyfte & operatorerna och | operatorerna definieras av följande tabell:

x y x & y x \| y
true true true true
true false false true
true null null true
false true false true
false false false false
false null false null
null true null true
null false false null
null null null null

Obs! Typen bool? liknar den trevärdestyp som används för booleska uttryck i SQL. Tabellen ovan följer samma semantik som SQL, medan tillämpningen av reglerna i §12.4.8 på operatörerna & och | inte skulle göra det. Reglerna i §12.4.8 tillhandahåller redan SQL-liknande semantik för den lyftade ^ operatorn. slutkommentar

12.14 Villkorsstyrda logiska operatorer

12.14.1 Allmänt

Operatorerna && och || kallas för villkorsstyrda logiska operatorer. De kallas också logiska operatorer för "kortslutning".

conditional_and_expression
    : inclusive_or_expression
    | conditional_and_expression '&&' inclusive_or_expression
    ;

conditional_or_expression
    : conditional_and_expression
    | conditional_or_expression '||' conditional_and_expression
    ;

Operatorerna && och || är villkorliga versioner av operatorerna & och | :

  • Åtgärden x && y motsvarar åtgärden x & y, förutom att y endast utvärderas om x inte falseär .
  • Åtgärden x || y motsvarar åtgärden x | y, förutom att y endast utvärderas om x inte trueär .

Obs! Anledningen till att kortslutning använder villkoren "inte sant" och "inte falskt" är att göra det möjligt för användardefinierade villkorsoperatorer att definiera när kortslutning gäller. Användardefinierade typer kan vara i ett tillstånd där operator true returnerar false och operator false returnerar false. I dessa fall skulle varken && eller || skulle kortslutning. slutkommentar

Om en operand av en villkorsstyrd logisk operator har kompileringstidstypen dynamicär uttrycket dynamiskt bundet (§12.3.3). I det här fallet är dynamickompileringstidstypen för uttrycket , och den lösning som beskrivs nedan sker vid körning med hjälp av körningstypen för de operander som har kompileringstidstypen dynamic.

En åtgärd av formuläret x && y eller x || y bearbetas genom att tillämpa överbelastningsmatchning (§12.4.5) som om åtgärden var skriven x & y eller x | y. Sedan kan du

  • Om det inte går att hitta en enda bästa operator, eller om överlagringsupplösningen väljer en av de fördefinierade heltals logiska operatorerna eller nullbara booleska logiska operatorer (§12.13.5), uppstår ett bindningstidsfel.
  • Annars, om den valda operatorn är en av de fördefinierade booleska logiska operatorerna (§12.13.4), bearbetas åtgärden enligt beskrivningen i §12.14.2.
  • I annat fall är den valda operatorn en användardefinierad operatör och åtgärden bearbetas enligt beskrivningen i §12.14.3.

Det går inte att överbelasta de villkorliga logiska operatorerna direkt. Men eftersom de villkorsstyrda logiska operatorerna utvärderas i termer av de vanliga logiska operatorerna anses överbelastningar av de vanliga logiska operatorerna, med vissa begränsningar, även överbelastningar av de villkorsstyrda logiska operatorerna. Detta beskrivs ytterligare i §12.14.3.

12.14.2 Booleska villkorsstyrda logiska operatorer

När operanderna av && eller || är av typen bool, eller när operanderna är av typer som inte definierar en tillämplig operator & eller operator |, men definierar implicita konverteringar till bool, bearbetas åtgärden enligt följande:

  • Åtgärden x && y utvärderas som x ? y : false. Med andra ord x utvärderas och konverteras först till typen bool. Sedan, om x är true, y utvärderas och konverteras till typ bool, och detta blir resultatet av åtgärden. Annars är falseresultatet av åtgärden .
  • Åtgärden x || y utvärderas som x ? true : y. Med andra ord x utvärderas och konverteras först till typen bool. x Om är trueär truesedan resultatet av åtgärden . Annars y utvärderas och konverteras till typen bool, och detta blir resultatet av åtgärden.

12.14.3 Användardefinierade villkorsstyrda logiska operatorer

När operander av && eller || är av typer som deklarerar en tillämplig användardefinierad operator & eller operator |, ska båda av följande vara sanna, där T är den typ där den valda operatorn deklareras:

  • Returtypen och typen av varje parameter för den valda operatorn ska vara T. Med andra ord ska operatorn beräkna den logiska OCH eller den logiska OR:en för två operander av typen T, och returnera ett resultat av typen T.
  • T skall innehålla deklarationer av operator true och operator false.

Ett bindningstidsfel uppstår om något av dessa krav inte uppfylls. Annars && utvärderas åtgärden eller || genom att kombinera den användardefinierade operator true eller operator false med den valda användardefinierade operatorn:

  • Åtgärden utvärderas som T.false(x) ? x : T.&(x, y), där T.false(x) är ett anrop av den operator false deklarerade i T, och T.&(x, y) är ett anrop för den valda operator &.x && y Med andra ord x utvärderas först och operator false anropas på resultatet för att avgöra om x är definitivt falskt. Om x är definitivt falskt är resultatet av åtgärden det värde som tidigare beräknats för x. Annars y utvärderas och det valda operator & anropas på det värde som tidigare beräknats för x och värdet som beräknas för för y att generera resultatet av åtgärden.
  • Åtgärden utvärderas som T.true(x) ? x : T.|(x, y), där T.true(x) är ett anrop av den operator true deklarerade i T, och T.|(x, y) är ett anrop för den valda operator |.x || y Med andra ord x utvärderas först och operator true anropas på resultatet för att avgöra om x är definitivt sant. x Om är definitivt sant är resultatet av åtgärden sedan värdet som tidigare beräknats för x. Annars y utvärderas och det valda operator | anropas på det värde som tidigare beräknats för x och värdet som beräknas för för y att generera resultatet av åtgärden.

I någon av dessa åtgärder utvärderas uttrycket som ges av x bara en gång, och uttrycket som anges av y utvärderas eller utvärderas antingen inte exakt en gång.

12.15 Operatorn null coalescing

Operatorn ?? kallas för null coalescing-operatorn.

null_coalescing_expression
    : conditional_or_expression
    | conditional_or_expression '??' null_coalescing_expression
    | throw_expression
    ;

I ett null-sammanslutande uttryck i formuläret a ?? b, om a är icke-null, är aresultatet ; annars är bresultatet . Åtgärden utvärderar b endast om a är null.

Operatorn null coalescing är höger-associativ, vilket innebär att åtgärder grupperas från höger till vänster.

Exempel: Ett uttryck för formuläret a ?? b ?? c utvärderas som en ?? (b ?? c). I allmänna termer returnerar ett uttryck för formuläret E1 ?? E2 ?? ... ?? EN den första av operanderna som intenull är eller null om alla operander är null. slutexempel

Typen av uttryck a ?? b beror på vilka implicita konverteringar som är tillgängliga på operanderna. I prioritetsordning är typen , , eller B, där A är typen av a (tillhandahålls som a har en typ), B typen av b(tillhandahålls som b har en typ) och A₀ är den underliggande typen av A om A är en nullbar värdetyp, eller A på annat sätt. Aa ?? b A₀ a ?? b Mer specifikt bearbetas följande:

  • Om A det finns och inte är en nullbar värdetyp eller en referenstyp uppstår ett kompileringsfel.
  • Annars, om A det finns och b är ett dynamiskt uttryck, är dynamicresultattypen . Vid körning a utvärderas först. Om a inte nullkonverteras , a konverteras till dynamicoch detta blir resultatet. Annars b utvärderas och detta blir resultatet.
  • Annars, om A finns och är en nullbar värdetyp och en implicit konvertering finns från b till A₀, är A₀resultattypen . Vid körning a utvärderas först. Om a är inte null, a packas upp för att skriva A₀, och detta blir resultatet. Annars b utvärderas och konverteras till typ A₀, och detta blir resultatet.
  • Annars, om A finns och en implicit konvertering finns från b till A, är Aresultattypen . Vid körning utvärderas en först. Om en inte är null blir ett resultat. Annars b utvärderas och konverteras till typ A, och detta blir resultatet.
  • Annars, om A finns och är en nullbar värdetyp, b har en typ B och en implicit konvertering finns från A₀ till B, är Bresultattypen . Vid körning a utvärderas först. Om a är inte null, a packas upp för att skriva A₀ och konverteras till typ B, och detta blir resultatet. Annars b utvärderas och blir resultatet.
  • Annars, om b har en typ B och en implicit konvertering finns från a till B, är Bresultattypen . Vid körning a utvärderas först. Om a inte nullkonverteras , a konverteras till typ B, och detta blir resultatet. Annars b utvärderas och blir resultatet.

Annars a b och är inkompatibla och a kompileringsfel inträffar.

12.16 Operatorn för utkastsuttryck

throw_expression
    : 'throw' null_coalescing_expression
    ;

En throw_expression genererar värdet som skapas genom att utvärdera null_coalescing_expression. Uttrycket ska implicit konverteras till System.Exception, och resultatet av utvärderingen av uttrycket konverteras till System.Exception innan det utlöses. Beteendet vid körning av utvärderingen av ett kastuttryck är detsamma som angetts för ett kastuttryck (§13.10.6).

En throw_expression har ingen typ. En throw_expression konverteras till varje typ av implicit kastkonvertering.

Ett utkastsuttryck ska endast förekomma i följande syntaktiska kontexter:

  • Som den andra eller tredje operanden för en ternary-villkorsoperator (?:).
  • Som den andra operanden för en null-sammanskolningsoperator (??).
  • Som kroppen av en uttrycksfyllig lambda eller medlem.

12.17 Deklarationsuttryck

Ett deklarationsuttryck deklarerar en lokal variabel.

declaration_expression
    : local_variable_type identifier
    ;

local_variable_type
    : type
    | 'var'
    ;

Simple_name _ anses också vara ett deklarationsuttryck om enkel namnsökning inte hittade någon associerad deklaration (§12.8.4). När det används som ett deklarationsuttryck _ kallas det enkelt att ignorera. Det är semantiskt likvärdigt med var _, men tillåts på fler platser.

Ett deklarationsuttryck ska endast förekomma i följande syntaktiska sammanhang:

  • Som en out argument_value i en argument_list.
  • Som ett enkelt kasserande _ som består av vänster sida av en enkel tilldelning (§12.21.2).
  • Som en tuple_element i en eller flera rekursivt kapslade tuple_expressions, vars yttersta del består av den vänstra sidan av en dekonstruerande tilldelning. En deconstruction_expression ger upphov till deklarationsuttryck i den här positionen, även om deklarationsuttrycken inte är syntaktiskt närvarande.

Obs! Det innebär att ett deklarationsuttryck inte kan parenteseras. slutkommentar

Det är ett fel för en implicit typvariabel som deklarerats med en declaration_expression som ska refereras inom argument_list där den deklareras.

Det är ett fel för en variabel som deklarerats med en declaration_expression som ska refereras i dekonstruktionstilldelningen där den inträffar.

Ett deklarationsuttryck som är ett enkelt ignorerande eller där local_variable_type är identifieraren var klassificeras som en implicit typvariabel. Uttrycket har ingen typ och typen av den lokala variabeln härleds baserat på den syntaktiska kontexten enligt följande:

  • I en argument_list är variabelns härledda typ den deklarerade typen av motsvarande parameter.
  • Som vänster sida av en enkel tilldelning är den härledda typen av variabel typen av den högra sidan av tilldelningen.
  • I en tuple_expression till vänster om en enkel tilldelning är den härledda typen av variabel typen av motsvarande tuppeln på höger sida (efter dekonstruktion) av tilldelningen.

I annat fall klassificeras deklarationsuttrycket som en explicit typ av variabel, och uttryckets typ samt den deklarerade variabeln ska vara den som anges av local_variable_type.

Ett deklarationsuttryck med identifieraren _ är ett ignorerande (§9.2.9.1) och ger inget namn på variabeln. Ett deklarationsuttryck med en annan identifierare än _ introducerar det namnet i det närmaste omslutande lokala variabeldeklarationsutrymmet (§7.3).

Exempel:

string M(out int i, string s, out bool b) { ... }

var s1 = M(out int i1, "One", out var b1);
Console.WriteLine($"{i1}, {b1}, {s1}");
// Error: i2 referenced within declaring argument list
var s2 = M(out var i2, M(out i2, "Two", out bool b2), out b2);
var s3 = M(out int _, "Three", out var _);

Deklarationen visar s1 både explicita och implicit inskrivna deklarationsuttryck. Den härledda typen av b1 beror bool på att det är typen av motsvarande utdataparameter i M1. Den efterföljande WriteLine kan komma åt i1 och b1, som har introducerats i omfånget för omslutning.

Deklarationen visar s2 ett försök att använda i2 i det kapslade anropet till M, som inte tillåts, eftersom referensen inträffar i argumentlistan där i2 deklarerades. Å andra sidan tillåts referensen till b2 i det slutliga argumentet, eftersom den inträffar efter slutet av den kapslade argumentlista där b2 deklarerades.

Deklarationen visar s3 användningen av både implicita och explicit inskrivna deklarationsuttryck som ignoreras. Eftersom ignoreranden inte deklarerar en namngiven variabel tillåts flera förekomster av identifieraren _ .

(int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5);

Det här exemplet visar användningen av implicita och explicit inskrivna deklarationsuttryck för både variabler och ignoreras i en dekonstruktionstilldelning. _ Simple_name motsvarar var _ när ingen deklaration av _ hittas.

void M1(out int i) { ... }

void M2(string _)
{
    M1(out _);      // Error: `_` is a string
    M1(out var _);
}

I de här exemplen visas användningen av var _ för att ange en implicit typad ignorera när _ den inte är tillgänglig, eftersom den anger en variabel i omfånget omslutande.

slutexempel

12.18 Villkorsstyrd operator

Operatorn ?: kallas för villkorsoperator. Det kallas ibland även för ternary-operatorn.

conditional_expression
    : null_coalescing_expression
    | null_coalescing_expression '?' expression ':' expression
    | null_coalescing_expression '?' 'ref' variable_reference ':'
      'ref' variable_reference
    ;

Ett utkastsuttryck (§12.16) tillåts inte i en villkorsoperator om ref det finns.

Ett villkorsuttryck för formuläret b ? x : y utvärderar först villkoret b. Sedan, om b är true, x utvärderas och blir resultatet av åtgärden. Annars y utvärderas och blir resultatet av åtgärden. Ett villkorsuttryck utvärderar aldrig både x och y.

Villkorsoperatorn är höger-associativ, vilket innebär att åtgärder grupperas från höger till vänster.

Exempel: Ett uttryck för formuläret a ? b : c ? d : e utvärderas som a ? b : (c ? d : e). slutexempel

Operatorns ?: första operande ska vara ett uttryck som implicit kan konverteras till bool, eller ett uttryck av en typ som implementerar operator true. Om inget av dessa krav uppfylls uppstår ett kompileringsfel.

Om ref finns:

  • Det ska finnas en identitetskonvertering mellan typerna av de två variable_references, och typen av resultat kan vara någon av typerna. Om någon av typerna är dynamicföredrar typinferens dynamic (§8.7). Om någon av typerna är en tupplar (§8.3.11) innehåller typinferens elementnamnen när elementnamnen i samma ordningsposition matchar i båda tupplar.
  • Resultatet är en variabelreferens som kan skrivas om båda variable_referenceär skrivbara.

Obs! När ref finns returnerar conditional_expression en variabelreferens som kan tilldelas till en referensvariabel med operatorn = ref eller skickas som en referens-/indata-/utdataparameter. slutkommentar

Om ref inte finns styr den andra och tredje operanderna och x y, för ?: operatorn typen av villkorsuttryck:

  • Om x har typ X och y har typ Y då,
    • Om det finns en identitetskonvertering mellan X och Yär resultatet den vanligaste typen av uttryck (§12.6.3.15). Om någon av typerna är dynamicföredrar typinferens dynamic (§8.7). Om någon av typerna är en tupplar (§8.3.11) innehåller typinferens elementnamnen när elementnamnen i samma ordningsposition matchar i båda tupplar.
    • Annars, om en implicit konvertering (§10.2) finns från X till Y, men inte från Y till X, Y så är typen av villkorsuttryck.
    • Om det annars finns en implicit uppräkningskonvertering (§10.2.4) från X till Yär det Y typen av villkorsuttryck.
    • Om det annars finns en implicit uppräkningskonvertering (§10.2.4) från Y till Xär det X typen av villkorsuttryck.
    • Annars, om en implicit konvertering (§10.2) finns från Y till X, men inte från X till Y, X så är typen av villkorsuttryck.
    • Annars kan ingen uttryckstyp fastställas och ett kompileringsfel inträffar.
  • Om bara en av x och y har en typ, och både x och y implicit kan konverteras till den typen, så är det typen av villkorsuttryck.
  • Annars kan ingen uttryckstyp fastställas och ett kompileringsfel inträffar.

Körningsbearbetningen av ett ref-villkorsuttryck i formuläret b ? ref x : ref y består av följande steg:

  • b Först utvärderas och bool värdet för bestämsb:
    • Om en implicit konvertering från typen till b bool finns utförs den implicita konverteringen för att skapa ett bool värde.
    • Annars anropas den operator true som definieras av typen för b för att skapa ett bool värde.
  • Om värdet bool som skapas av steget ovan är trueutvärderas och x den resulterande variabelreferensen blir resultatet av villkorsuttrycket.
  • Annars y utvärderas och den resulterande variabelreferensen blir resultatet av villkorsuttrycket.

Körningsbearbetningen av ett villkorsuttryck i formuläret b ? x : y består av följande steg:

  • b Först utvärderas och bool värdet för bestämsb:
    • Om en implicit konvertering från typen till b bool finns utförs den implicita konverteringen för att skapa ett bool värde.
    • Annars anropas den operator true som definieras av typen för b för att skapa ett bool värde.
  • Om värdet bool som skapas av steget ovan är truex utvärderas och konverteras till typen av villkorsuttryck, och detta blir resultatet av villkorsuttrycket.
  • Annars y utvärderas och konverteras till typen av villkorsuttryck, och detta blir resultatet av villkorsuttrycket.

12.19 Anonyma funktionsuttryck

12.19.1 Allmänt

En anonym funktion är ett uttryck som representerar en "in-line"-metoddefinition. En anonym funktion har inte ett värde eller en typ i sig själv, men den kan konverteras till en kompatibel delegat- eller uttrycksträdstyp. Utvärderingen av en anonym funktionskonvertering beror på måltypen för konverteringen: Om det är en delegattyp utvärderas konverteringen till ett ombudsvärde som refererar till den metod som den anonyma funktionen definierar. Om det är en uttrycksträdstyp utvärderas konverteringen till ett uttrycksträd som representerar metodens struktur som en objektstruktur.

Obs! Av historiska skäl finns det två syntaktiska varianter av anonyma funktioner, nämligen lambda_expressions och anonymous_method_expressions. I nästan alla syften är lambda_expressions mer koncisa och uttrycksfulla än anonymous_method_expressions, som förblir i språket för bakåtkompatibilitet. slutkommentar

lambda_expression
    : 'async'? anonymous_function_signature '=>' anonymous_function_body
    ;

anonymous_method_expression
    : 'async'? 'delegate' explicit_anonymous_function_signature? block
    ;

anonymous_function_signature
    : explicit_anonymous_function_signature
    | implicit_anonymous_function_signature
    ;

explicit_anonymous_function_signature
    : '(' explicit_anonymous_function_parameter_list? ')'
    ;

explicit_anonymous_function_parameter_list
    : explicit_anonymous_function_parameter
      (',' explicit_anonymous_function_parameter)*
    ;

explicit_anonymous_function_parameter
    : anonymous_function_parameter_modifier? type identifier
    ;

anonymous_function_parameter_modifier
    : 'ref'
    | 'out'
    | 'in'
    ;

implicit_anonymous_function_signature
    : '(' implicit_anonymous_function_parameter_list? ')'
    | implicit_anonymous_function_parameter
    ;

implicit_anonymous_function_parameter_list
    : implicit_anonymous_function_parameter
      (',' implicit_anonymous_function_parameter)*
    ;

implicit_anonymous_function_parameter
    : identifier
    ;

anonymous_function_body
    : null_conditional_invocation_expression
    | expression
    | 'ref' variable_reference
    | block
    ;

Vid erkännande av en anonymous_function_body om både null_conditional_invocation_expression och uttrycksalternativ är tillämpliga, ska den förra väljas.

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

Obs! När det behandlas som ett uttryck är en syntaktisk form som x?.M() ett fel om resultattypen M är void (§12.8.13). Men när den behandlas som en null_conditional_invocation_expression tillåts resultattypen vara void. slutkommentar

Exempel: Resultattypen List<T>.Reverse är void. I följande kod är brödtexten i det anonyma uttrycket en null_conditional_invocation_expression, så det är inte ett fel.

Action<List<int>> a = x => x?.Reverse();

slutexempel

Operatorn => har samma prioritet som tilldelning (=) och är rätt associativ.

En anonym funktion med async modifieraren är en asynkron funktion och följer de regler som beskrivs i §15.15.

Parametrarna för en anonym funktion i form av en lambda_expression kan skrivas explicit eller implicit. I en uttryckligen angiven parameterlista anges typen av varje parameter explicit. I en implicit typ av parameterlista härleds typerna av parametrarna från kontexten där den anonyma funktionen inträffar, särskilt när den anonyma funktionen konverteras till en kompatibel ombudstyp eller uttrycksträdstyp, ger den typen parametertyperna (§10.7).

I en lambda_expression med en enda, implicit typad parameter kan parenteserna utelämnas från parameterlistan. Med andra ord en anonym funktion i formuläret

( «param» ) => «expr»

kan förkortas till

«param» => «expr»

Parameterlistan för en anonym funktion i form av en anonymous_method_expression är valfri. Om detta anges ska parametrarna uttryckligen skrivas. Annars kan den anonyma funktionen konverteras till ett ombud med en parameterlista som inte innehåller utdataparametrar.

En blocktext av en anonym funktion kan alltid nås (§13.2).

Exempel: Några exempel på anonyma funktioner följer nedan:

x => x + 1                             // Implicitly typed, expression body
x => { return x + 1; }                 // Implicitly typed, block body
(int x) => x + 1                       // Explicitly typed, expression body
(int x) => { return x + 1; }           // Explicitly typed, block body
(x, y) => x * y                        // Multiple parameters
() => Console.WriteLine()              // No parameters
async (t1,t2) => await t1 + await t2   // Async
delegate (int x) { return x + 1; }     // Anonymous method expression
delegate { return 1 + 1; }             // Parameter list omitted

slutexempel

Beteendet för lambda_expressions och anonymous_method_expressions är detsamma förutom följande punkter:

  • anonymous_method_expression tillåter att parameterlistan utelämnas helt och hållet, vilket ger konverteringsbarhet för att delegera typer av valfri lista med värdeparametrar.
  • lambda_expression s tillåter att parametertyper utelämnas och härleds medan anonymous_method_expressionkräver att parametertyper uttryckligen anges.
  • Brödtexten i en lambda_expression kan vara ett uttryck eller ett block, medan brödtexten i en anonymous_method_expression ska vara ett block.
  • Endast lambda_expressionhar konverteringar till kompatibla uttrycksträdstyper (§8.6).

12.19.2 Anonyma funktionssignaturer

Anonymous_function_signature för en anonym funktion definierar namnen och eventuellt typerna av parametrarna för den anonyma funktionen. Omfånget för parametrarna för den anonyma funktionen är anonymous_function_body (§7.7). Tillsammans med parameterlistan (om den ges) utgör anonym-metod-brödtexten ett deklarationsutrymme (§7.3). Det är därför ett kompileringsfel för namnet på en parameter för den anonyma funktionen för att matcha namnet på en lokal variabel, lokal konstant eller parameter vars omfång omfattar anonymous_method_expression eller lambda_expression.

Om en anonym funktion har en explicit_anonymous_function_signature begränsas uppsättningen med kompatibla ombudstyper och uttrycksträdstyper till de som har samma parametertyper och modifierare i samma ordning (§10.7). Till skillnad från metodgruppkonverteringar (§10.8) stöds inte kontraavvikelse för anonyma funktionsparametertyper. Om en anonym funktion inte har någon anonymous_function_signature begränsas uppsättningen med kompatibla ombudstyper och uttrycksträdstyper till de som inte har några utdataparametrar.

Observera att en anonymous_function_signature inte kan innehålla attribut eller en parametermatris. Ett anonymous_function_signature kan dock vara kompatibelt med en ombudstyp vars parameterlista innehåller en parametermatris.

Observera också att konverteringen till en uttrycksträdstyp, även om den är kompatibel, fortfarande kan misslyckas vid kompileringstid (§8.6).

12.19.3 Anonyma funktionskroppar

Brödtexten (uttrycket eller blocket) för en anonym funktion omfattas av följande regler:

  • Om den anonyma funktionen innehåller en signatur är parametrarna som anges i signaturen tillgängliga i brödtexten. Om den anonyma funktionen inte har någon signatur kan den konverteras till en ombudstyp eller uttryckstyp med parametrar (§10.7), men parametrarna kan inte nås i brödtexten.
  • Med undantag för bireferensparametrar som anges i signaturen (om någon) för den närmaste omslutande anonyma funktionen är det ett kompileringsfel för brödtexten att komma åt en bireferensparameter.
  • Förutom parametrar som anges i signaturen (om någon) för den närmaste omslutande anonyma funktionen är det ett kompileringsfel för brödtexten att komma åt en parameter av en ref struct typ.
  • När typen av this är en struct-typ är det ett kompileringsfel för brödtexten att komma åt this. Detta gäller om åtkomsten är explicit (som i this.x) eller implicit (som i var x är en instansmedlem i x struct). Den här regeln förbjuder helt enkelt sådan åtkomst och påverkar inte om medlemssökning resulterar i en medlem i structen.
  • Brödtexten har åtkomst till de yttre variablerna (§12.19.6) för den anonyma funktionen. Åtkomsten till en yttre variabel refererar till instansen av variabeln som är aktiv vid den tidpunkt då lambda_expression eller anonymous_method_expression utvärderas (§12.19.7).
  • Det är ett kompileringsfel som gör att brödtexten innehåller en goto -instruktion, en break -instruktion eller en continue -instruktion vars mål ligger utanför brödtexten eller i brödtexten för en innesluten anonym funktion.
  • En return instruktion i brödtexten returnerar kontroll från ett anrop av närmaste omslutande anonyma funktion, inte från den omslutande funktionsmedlemmen.

Det är uttryckligen ospecificerat om det finns något sätt att köra blocket för en anonym funktion annat än genom utvärdering och anrop av lambda_expression eller anonymous_method_expression. Kompilatorn kan i synnerhet välja att implementera en anonym funktion genom att syntetisera en eller flera namngivna metoder eller typer. Namnen på sådana syntetiserade element skall vara av ett formulär som är reserverat för kompilatoranvändning (§6.4.3).

12.19.4 Överbelastningsupplösning

Anonyma funktioner i en argumentlista deltar i typinferens och överlagringsmatchning. Se §12.6.3 och §12.6.4 för de exakta reglerna.

Exempel: I följande exempel visas effekten av anonyma funktioner på överbelastningsupplösningen.

class ItemList<T> : List<T>
{
    public int Sum(Func<T, int> selector)
    {
        int sum = 0;
        foreach (T item in this)
        {
            sum += selector(item);
        }
        return sum;
    }

    public double Sum(Func<T, double> selector)
    {
        double sum = 0;
        foreach (T item in this)
        {
            sum += selector(item);
        }
        return sum;
    }
}

Klassen ItemList<T> har två Sum metoder. Var och en tar ett selector argument som extraherar värdet som ska summeras från ett listobjekt. Det extraherade värdet kan vara antingen en int eller en double och den resulterande summan är på samma sätt antingen en int eller en double.

Metoderna Sum kan till exempel användas för att beräkna summor från en lista med detaljrader i en ordning.

class Detail
{
    public int UnitCount;
    public double UnitPrice;
    ...
}

class A
{
    void ComputeSums()
    {
        ItemList<Detail> orderDetails = GetOrderDetails( ... );
        int totalUnits = orderDetails.Sum(d => d.UnitCount);
        double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount);
        ...
    }

    ItemList<Detail> GetOrderDetails( ... )
    {
        ...
    }
}

I den första anropet av orderDetails.Sumgäller båda Sum metoderna eftersom den anonyma funktionen d => d.UnitCount är kompatibel med både Func<Detail,int> och Func<Detail,double>. Överlagringsmatchningen väljer dock den första Sum metoden eftersom konverteringen till Func<Detail,int> är bättre än konverteringen till Func<Detail,double>.

I den andra anropet av orderDetails.Sumgäller endast den andra Sum metoden eftersom den anonyma funktionen d => d.UnitPrice * d.UnitCount genererar ett värde av typen double. Överbelastningsupplösningen väljer därför den andra Sum metoden för det anropet.

slutexempel

12.19.5 Anonyma funktioner och dynamisk bindning

En anonym funktion får inte vara mottagare, argument eller operand för en dynamiskt bunden åtgärd.

12.19.6 Yttre variabler

12.19.6.1 Allmänt

Alla lokala variabler, värdeparametrar eller parametermatriser vars omfång omfattar lambda_expression eller anonymous_method_expression kallas för en yttre variabel för den anonyma funktionen. I en instansfunktionsmedlem i en klass betraktas det här värdet som en värdeparameter och är en yttre variabel för alla anonyma funktioner som ingår i funktionsmedlemmen.

12.19.6.2 Insamlade yttre variabler

När en yttre variabel refereras till av en anonym funktion, sägs den yttre variabeln ha avbildats av den anonyma funktionen. Normalt är livslängden för en lokal variabel begränsad till körning av blocket eller -instruktionen som den är associerad med (§9.2.9). Livslängden för en insamlad yttre variabel utökas dock åtminstone tills det ombuds- eller uttrycksträd som skapats från den anonyma funktionen blir berättigat till skräpinsamling.

Exempel: I exemplet

delegate int D();

class Test
{
    static D F()
    {
        int x = 0;
        D result = () => ++x;
        return result;
    }

    static void Main()
    {
        D d = F();
        Console.WriteLine(d());
        Console.WriteLine(d());
        Console.WriteLine(d());
    }
}

den lokala variabeln x registreras av den anonyma funktionen och livslängden x för utökas åtminstone tills ombudet som returneras från F blir berättigad till skräpinsamling. Eftersom varje anrop av den anonyma funktionen fungerar på samma instans av xär utdata från exemplet:

1
2
3

slutexempel

När en lokal variabel eller en värdeparameter registreras av en anonym funktion anses den lokala variabeln eller parametern inte längre vara en fast variabel (§23.4), utan anses i stället vara en flyttbar variabel. Insamlade yttre variabler kan dock inte användas i en fixed instruktion (§23.7), så adressen för en infångade yttre variabel kan inte tas.

Obs! Till skillnad från en inkapslade variabel kan en insamlad lokal variabel samtidigt exponeras för flera körningstrådar. slutkommentar

12.19.6.3 Instansiering av lokala variabler

En lokal variabel anses vara instansierad när körningen anger variabelns omfång.

Exempel: När följande metod anropas instansieras och initieras den lokala variabeln x tre gånger – en gång för varje iteration av loopen.

static void F()
{
    for (int i = 0; i < 3; i++)
    {
        int x = i * 2 + 1;
        ...
    }
}

Om du flyttar deklarationen x utanför loopen resulterar det dock i en enda instansiering av x:

static void F()
{
    int x;
    for (int i = 0; i < 3; i++)
    {
        x = i * 2 + 1;
        ...
    }
}

slutexempel

När den inte registreras finns det inget sätt att se exakt hur ofta en lokal variabel instansieras – eftersom instansieringarnas livslängd är uppdelade är det möjligt för varje instansation att helt enkelt använda samma lagringsplats. Men när en anonym funktion fångar en lokal variabel blir effekterna av instansiering uppenbara.

Exempel: Exemplet

delegate void D();
class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        for (int i = 0; i < 3; i++)
        {
            int x = i * 2 + 1;
            result[i] = () => Console.WriteLine(x);
        }
        return result;
    }

    static void Main()
    {
        foreach (D d in F())
        {
            d();
        }
    }
}

genererar utdata:

1
3
5

Men när deklarationen av x flyttas utanför loopen:

delegate void D();

class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        int x;
        for (int i = 0; i < 3; i++)
        {
            x = i * 2 + 1;
            result[i] = () => Console.WriteLine(x);
        }
        return result;
   }

   static void Main()
   {
       foreach (D d in F())
       {
           d();
       }
   }
}

utdata är:

5
5
5

Observera att kompilatorn tillåts (men inte krävs) för att optimera de tre instanserna i en enda delegatinstans (§10.7.2).

slutexempel

Om en for-loop deklarerar en iterationsvariabel anses själva variabeln deklareras utanför loopen.

Exempel: Om exemplet ändras för att avbilda själva iterationsvariabeln:

delegate void D();

class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        for (int i = 0; i < 3; i++)
        {
            result[i] = () => Console.WriteLine(i);
        }
        return result;
   }

   static void Main()
   {
       foreach (D d in F())
       {
           d();
       }
   }
}

Endast en instans av iterationsvariabeln registreras, vilket ger utdata:

3
3
3

slutexempel

Det är möjligt för anonyma funktionsdelegater att dela vissa insamlade variabler men ändå ha separata instanser av andra.

Exempel: Om F ändras till

static D[] F()
{
    D[] result = new D[3];
    int x = 0;
    for (int i = 0; i < 3; i++)
    {
        int y = 0;
        result[i] = () => Console.WriteLine($"{++x} {++y}");
    }
    return result;
}

de tre ombuden samlar in samma instans av x men separata instanser av y, och utdata är:

1 1
2 1
3 1

slutexempel

Separata anonyma funktioner kan avbilda samma instans av en yttre variabel.

Exempel: I exemplet:

delegate void Setter(int value);
delegate int Getter();

class Test
{
    static void Main()
    {
        int x = 0;
        Setter s = (int value) => x = value;
        Getter g = () => x;
        s(5);
        Console.WriteLine(g());
        s(10);
        Console.WriteLine(g());
    }
}

de två anonyma funktionerna avbildar samma instans av den lokala variabeln x, och de kan därför "kommunicera" via den variabeln. Utdata från exemplet är:

5
10

slutexempel

12.19.7 Utvärdering av anonyma funktionsuttryck

En anonym funktion F ska alltid konverteras till en ombudstyp D eller en uttrycksträdstyp E, antingen direkt eller genom körningen av ett uttryck new D(F)för att skapa ombud . Denna konvertering avgör resultatet av den anonyma funktionen enligt beskrivningen i §10.7.

12.19.8 Implementeringsexempel

Den här underklienten är informativ.

Den här delklienten beskriver en möjlig implementering av anonyma funktionskonverteringar när det gäller andra C#-konstruktioner. Implementeringen som beskrivs här baseras på samma principer som används av en kommersiell C#-kompilator, men det är inte på något sätt en föreskriven implementering, och det är inte heller den enda möjliga. Den nämner bara kort konverteringar till uttrycksträd, eftersom deras exakta semantik ligger utanför den här specifikationens omfång.

Resten av den här underklienten innehåller flera exempel på kod som innehåller anonyma funktioner med olika egenskaper. För varje exempel tillhandahålls en motsvarande översättning till kod som endast använder andra C#-konstruktioner. I exemplen antas identifieraren D av representera följande ombudstyp:

public delegate void D();

Den enklaste formen av en anonym funktion är en som inte fångar upp några yttre variabler:

delegate void D();

class Test
{
    static void F()
    {
        D d = () => Console.WriteLine("test");
    }
}

Detta kan översättas till en delegat-instansiering som refererar till en kompilatorgenererad statisk metod där koden för den anonyma funktionen placeras:

delegate void D();

class Test
{
    static void F()
    {
        D d = new D(__Method1);
    }

    static void __Method1()
    {
        Console.WriteLine("test");
    }
}

I följande exempel refererar den anonyma funktionen till instansmedlemmar thisi :

delegate void D();

class Test
{
    int x;

    void F()
    {
        D d = () => Console.WriteLine(x);
    }
}

Detta kan översättas till en kompilatorgenererad instansmetod som innehåller koden för den anonyma funktionen:

delegate void D();

class Test
{
   int x;

   void F()
   {
       D d = new D(__Method1);
   }

   void __Method1()
   {
       Console.WriteLine(x);
   }
}

I det här exemplet samlar den anonyma funktionen in en lokal variabel:

delegate void D();

class Test
{
    void F()
    {
        int y = 123;
        D d = () => Console.WriteLine(y);
    }
}

Livslängden för den lokala variabeln måste nu utökas till minst livslängden för den anonyma funktionsdelegaten. Detta kan uppnås genom att "hissa" den lokala variabeln till ett fält i en kompilatorgenererad klass. Instansiering av den lokala variabeln (§12.19.6.3) motsvarar sedan att skapa en instans av den kompilatorgenererade klassen och att komma åt den lokala variabeln motsvarar åtkomst till ett fält i instansen av den kompilatorgenererade klassen. Dessutom blir den anonyma funktionen en instansmetod för den kompilatorgenererade klassen:

delegate void D();

class Test
{
    void F()
    {
        __Locals1 __locals1 = new __Locals1();
        __locals1.y = 123;
        D d = new D(__locals1.__Method1);
    }

    class __Locals1
    {
        public int y;

        public void __Method1()
        {
            Console.WriteLine(y);
        }
    }
}

Slutligen samlar följande anonyma funktion in this samt två lokala variabler med olika livslängder:

delegate void D();

class Test
{
   int x;

   void F()
   {
       int y = 123;
       for (int i = 0; i < 10; i++)
       {
           int z = i * 2;
           D d = () => Console.WriteLine(x + y + z);
       }
   }
}

Här skapas en kompilatorgenererad klass för varje block där lokalbefolkningen samlas in så att lokalbefolkningen i de olika blocken kan ha oberoende livslängder. En instans av __Locals2, den kompilatorgenererade klassen för det inre blocket, innehåller den lokala variabeln z och ett fält som refererar till en instans av __Locals1. En instans av __Locals1, den kompilatorgenererade klassen för det yttre blocket, innehåller den lokala variabeln y och ett fält som refererar till this den omslutande funktionsmedlemmen. Med dessa datastrukturer är det möjligt att nå alla insamlade yttre variabler via en instans av __Local2, och koden för den anonyma funktionen kan därför implementeras som en instansmetod för den klassen.

delegate void D();

class Test
{
    int x;

    void F()
    {
        __Locals1 __locals1 = new __Locals1();
        __locals1.__this = this;
        __locals1.y = 123;
        for (int i = 0; i < 10; i++)
        {
            __Locals2 __locals2 = new __Locals2();
            __locals2.__locals1 = __locals1;
            __locals2.z = i * 2;
            D d = new D(__locals2.__Method1);
        }
    }

    class __Locals1
    {
        public Test __this;
        public int y;
    }

    class __Locals2
    {
        public __Locals1 __locals1;
        public int z;

        public void __Method1()
        {
            Console.WriteLine(__locals1.__this.x + __locals1.y + z);
        }
    }
}

Samma teknik som används här för att samla in lokala variabler kan också användas när du konverterar anonyma funktioner till uttrycksträd: referenser till de kompilatorgenererade objekten kan lagras i uttrycksträdet och åtkomst till de lokala variablerna kan representeras som fältåtkomster på dessa objekt. Fördelen med den här metoden är att den gör att de "lyfte" lokala variablerna kan delas mellan ombud och uttrycksträd.

Slut på informativ text.

12.20 Frågeuttryck

12.20.1 Allmänt

Frågeuttryck ger en språkintegrerad syntax för frågor som liknar relations- och hierarkiska frågespråk som SQL och XQuery.

query_expression
    : from_clause query_body
    ;

from_clause
    : 'from' type? identifier 'in' expression
    ;

query_body
    : query_body_clauses? select_or_group_clause query_continuation?
    ;

query_body_clauses
    : query_body_clause
    | query_body_clauses query_body_clause
    ;

query_body_clause
    : from_clause
    | let_clause
    | where_clause
    | join_clause
    | join_into_clause
    | orderby_clause
    ;

let_clause
    : 'let' identifier '=' expression
    ;

where_clause
    : 'where' boolean_expression
    ;

join_clause
    : 'join' type? identifier 'in' expression 'on' expression
      'equals' expression
    ;

join_into_clause
    : 'join' type? identifier 'in' expression 'on' expression
      'equals' expression 'into' identifier
    ;

orderby_clause
    : 'orderby' orderings
    ;

orderings
    : ordering (',' ordering)*
    ;

ordering
    : expression ordering_direction?
    ;

ordering_direction
    : 'ascending'
    | 'descending'
    ;

select_or_group_clause
    : select_clause
    | group_clause
    ;

select_clause
    : 'select' expression
    ;

group_clause
    : 'group' expression 'by' expression
    ;

query_continuation
    : 'into' identifier query_body
    ;

Ett frågeuttryck börjar med en from -sats och slutar med antingen en select eller group -sats. Den inledande from satsen kan följas av noll eller fler from, let, whereeller join orderby -satser. Varje from sats är en generator som introducerar en intervallvariabel som sträcker sig över elementen i en sekvens. Varje let sats introducerar en intervallvariabel som representerar ett värde som beräknas med hjälp av tidigare intervallvariabler. Varje where sats är ett filter som exkluderar objekt från resultatet. Varje join sats jämför angivna nycklar i källsekvensen med nycklar för en annan sekvens, vilket ger matchande par. Varje orderby sats ordnar om objekt enligt angivna villkor. select Slutsatsen eller group -satsen anger resultatets form i termer av intervallvariablerna. Slutligen kan en into sats användas för att "skarva" frågor genom att behandla resultatet av en fråga som en generator i en efterföljande fråga.

12.20.2 Tvetydigheter i frågeuttryck

Frågeuttryck använder ett antal kontextuella nyckelord (§6.4.4): ascending, by, descending, equals, from, group, into, join, let, on, , orderbyoch select where.

För att undvika tvetydigheter som kan uppstå vid användning av dessa identifierare både som nyckelord och enkla namn betraktas dessa identifierare som nyckelord var som helst i ett frågeuttryck, såvida de inte är prefix med "@" (§6.4.4) i vilket fall de betraktas som identifierare. För detta ändamål är ett frågeuttryck alla uttryck som börjar med "from identifierare" följt av alla token utom ";", "=" eller ",".

12.20.3 Översättning av frågeuttryck

12.20.3.1 Allmänt

C#-språket anger inte körningssemantiken för frågeuttryck. I stället översätts frågeuttryck till anrop av metoder som följer frågeuttrycksmönstret (§12.20.4). Mer specifikt översätts frågeuttryck till anrop av metoder med namnet Where, , Select, SelectManyJoin, GroupJoin, , OrderBy, OrderByDescending, ThenByThenByDescending, , GroupByoch Cast. Dessa metoder förväntas ha särskilda signaturer och returtyper enligt beskrivningen i §12.20.4. Dessa metoder kan vara instansmetoder för objektet som efterfrågas eller tilläggsmetoder som är externa för objektet. Dessa metoder implementerar den faktiska körningen av frågan.

Översättningen från frågeuttryck till metodanrop är en syntaktisk mappning som inträffar innan någon typbindning eller överlagringsmatchning har utförts. Efter översättning av frågeuttryck bearbetas de resulterande metodanropen som vanliga metodanrop, och detta kan i sin tur avslöja kompileringstidsfel. Dessa felvillkor omfattar, men är inte begränsade till, metoder som inte finns, argument av fel typer och generiska metoder där typinferens misslyckas.

Ett frågeuttryck bearbetas genom att upprepade gånger tillämpa följande översättningar tills inga ytterligare minskningar är möjliga. Översättningarna visas i programordning: varje avsnitt förutsätter att översättningarna i föregående avsnitt har utförts fullständigt, och när de är uttömda kommer ett avsnitt inte senare att ses över i bearbetningen av samma frågeuttryck.

Det är ett kompileringstidsfel för ett frågeuttryck att inkludera en tilldelning till en intervallvariabel eller användningen av en intervallvariabel som argument för en referens- eller utdataparameter.

Vissa översättningar matar in intervallvariabler med transparenta identifierare som anges av *. Dessa beskrivs ytterligare i §12.20.3.8.

12.20.3.2 Frågeuttryck med fortsättningar

Ett frågeuttryck med en fortsättning som följer dess frågetext

from «x1» in «e1» «b1» into «x2» «b2»

översätts till

from «x2» in ( from «x1» in «e1» «b1» ) «b2»

Översättningarna i följande avsnitt förutsätter att frågor inte har några fortsättningar.

Exempel: Exemplet:

from c in customers
group c by c.Country into g
select new { Country = g.Key, CustCount = g.Count() }

översätts till:

from g in
   (from c in customers
   group c by c.Country)
select new { Country = g.Key, CustCount = g.Count() }

den slutliga översättningen av vars

customers.
GroupBy(c => c.Country).
Select(g => new { Country = g.Key, CustCount = g.Count() })

slutexempel

12.20.3.3 Explicita intervallvariabeltyper

En from sats som uttryckligen anger en intervallvariabeltyp

from «T» «x» in «e»

översätts till

from «x» in ( «e» ) . Cast < «T» > ( )

En join sats som uttryckligen anger en intervallvariabeltyp

join «T» «x» in «e» on «k1» equals «k2»

översätts till

join «x» in ( «e» ) . Cast < «T» > ( ) on «k1» equals «k2»

Översättningarna i följande avsnitt förutsätter att frågor inte har några explicita intervallvariabeltyper.

Exempel: Exemplet

from Customer c in customers
where c.City == "London"
select c

översätts till

from c in (customers).Cast<Customer>()
where c.City == "London"
select c

den slutliga översättningen av

customers.
Cast<Customer>().
Where(c => c.City == "London")

slutexempel

Obs! Explicita intervallvariabeltyper är användbara för att fråga samlingar som implementerar det icke-generiska IEnumerable gränssnittet, men inte det allmänna IEnumerable<T> gränssnittet. I exemplet ovan skulle detta vara fallet om kunderna var av typen ArrayList. slutkommentar

12.20.3.4 Degenerera frågeuttryck

Ett frågeuttryck för formuläret

from «x» in «e» select «x»

översätts till

( «e» ) . Select ( «x» => «x» )

Exempel: Exemplet

from c in customers
select c

översätts till

(customers).Select(c => c)

slutexempel

Ett degenererat frågeuttryck är ett som trivialt väljer elementen i källan.

Obs! Senare faser av översättningen (§12.20.3.6 och §12.20.3.7) tar bort degenererade frågor som introduceras genom andra översättningssteg genom att ersätta dem med källan. Det är dock viktigt att se till att resultatet av ett frågeuttryck aldrig är själva källobjektet. Annars kan resultatet av en sådan fråga oavsiktligt exponera privata data (t.ex. en elementmatris) för en anropare. Därför skyddar det här steget degenererade frågor som skrivits direkt i källkoden genom att uttryckligen anropa Select källan. Det är sedan upp till implementerarna för Select och andra frågeoperatorer att se till att dessa metoder aldrig returnerar själva källobjektet. slutkommentar

12.20.3.5 Från, låt, var, kopplings- och orderby-satser

Ett frågeuttryck med en andra from sats följt av en select sats

from «x1» in «e1»  
from «x2» in «e2»  
select «v»

översätts till

( «e1» ) . SelectMany( «x1» => «e2» , ( «x1» , «x2» ) => «v» )

Exempel: Exemplet

from c in customers
from o in c.Orders
select new { c.Name, o.OrderID, o.Total }

översätts till

(customers).
SelectMany(c => c.Orders,
(c,o) => new { c.Name, o.OrderID, o.Total }
)

slutexempel

Ett frågeuttryck med en andra from sats följt av en frågetext Q som innehåller en icke-tom uppsättning frågetextsatser:

from «x1» in «e1»
from «x2» in «e2»
Q

översätts till

from * in («e1») . SelectMany( «x1» => «e2» ,
                              ( «x1» , «x2» ) => new { «x1» , «x2» } )
Q

Exempel: Exemplet

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

översätts till

from * in (customers).
   SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

den slutliga översättningen av

customers.
SelectMany(c => c.Orders, (c,o) => new { c, o }).
OrderByDescending(x => x.o.Total).
Select(x => new { x.c.Name, x.o.OrderID, x.o.Total })

där x är en kompilatorgenererad identifierare som annars är osynlig och otillgänglig.

slutexempel

Ett let uttryck tillsammans med dess föregående sats from :

from «x» in «e»  
let «y» = «f»  
...

översätts till

from * in ( «e» ) . Select ( «x» => new { «x» , «y» = «f» } )  
...

Exempel: Exemplet

from o in orders
let t = o.Details.Sum(d => d.UnitPrice * d.Quantity)
where t >= 1000
select new { o.OrderID, Total = t }

översätts till

from * in (orders).Select(
    o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
where t >= 1000
select new { o.OrderID, Total = t }

den slutliga översättningen av

orders
    .Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
    .Where(x => x.t >= 1000)
    .Select(x => new { x.o.OrderID, Total = x.t })

där x är en kompilatorgenererad identifierare som annars är osynlig och otillgänglig.

slutexempel

Ett where uttryck tillsammans med dess föregående sats from :

from «x» in «e»  
where «f»  
...

översätts till

from «x» in ( «e» ) . Where ( «x» => «f» )  
...

En join sats omedelbart följt av en select sats

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
select «v»

översätts till

( «e1» ) . Join( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «x2» ) => «v» )

Exempel: Exemplet

from c in customersh
join o in orders on c.CustomerID equals o.CustomerID
select new { c.Name, o.OrderDate, o.Total }

översätts till

(customers).Join(
   orders,
   c => c.CustomerID, o => o.CustomerID,
   (c, o) => new { c.Name, o.OrderDate, o.Total })

slutexempel

En join sats följt av en frågetextsats:

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
...

översätts till

from * in ( «e1» ) . Join(  
«e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «x2» ) => new { «x1» , «x2» })  
...

En join-into sats omedelbart följt av en select sats

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into «g»  
select «v»

översätts till

( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» ,
                     ( «x1» , «g» ) => «v» )

En join into sats följt av en frågetextsats

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into *g»  
...

översätts till

from * in ( «e1» ) . GroupJoin(  
   «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «g» ) => new { «x1» , «g» })
...

Exempel: Exemplet

from c in customers
join o in orders on c.CustomerID equals o.CustomerID into co
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }

översätts till

from * in (customers).GroupJoin(
    orders,
    c => c.CustomerID,
    o => o.CustomerID,
    (c, co) => new { c, co })
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }

den slutliga översättningen av

customers
    .GroupJoin(
        orders,
        c => c.CustomerID,
        o => o.CustomerID,
        (c, co) => new { c, co })
    .Select(x => new { x, n = x.co.Count() })
    .Where(y => y.n >= 10)
    .Select(y => new { y.x.c.Name, OrderCount = y.n })

där x och y är kompilatorgenererade identifierare som annars är osynliga och otillgängliga.

slutexempel

En orderby sats och dess föregående sats from :

from «x» in «e»  
orderby «k1» , «k2» , ... , «kn»  
...

översätts till

from «x» in ( «e» ) .
OrderBy ( «x» => «k1» ) .
ThenBy ( «x» => «k2» ) .
... .
ThenBy ( «x» => «kn» )
...

Om en ordering sats anger en indikator för fallande riktning skapas i stället ett anrop av OrderByDescending eller ThenByDescending .

Exempel: Exemplet

from o in orders
orderby o.Customer.Name, o.Total descending
select o

har den slutliga översättningen

(orders)
    .OrderBy(o => o.Customer.Name)
    .ThenByDescending(o => o.Total)

slutexempel

Följande översättningar förutsätter att det inte finns några let, whereeller orderby join -satser och inte mer än en inledande from sats i varje frågeuttryck.

12.20.3.6 Välj satser

Ett frågeuttryck för formuläret

from «x» in «e» select «v»

översätts till

( «e» ) . Select ( «x» => «v» )

utom när «v» är identifieraren «x»är översättningen helt enkelt

( «e» )

Exempel: Exemplet

from c in customers.Where(c => c.City == "London")
select c

översätts helt enkelt till

(customers).Where(c => c.City == "London")

slutexempel

12.20.3.7 Gruppsatser

En group sats

from «x» in «e» group «v» by «k»

översätts till

( «e» ) . GroupBy ( «x» => «k» , «x» => «v» )

utom när «v» är identifieraren «x»är översättningen

( «e» ) . GroupBy ( «x» => «k» )

Exempel: Exemplet

from c in customers
group c.Name by c.Country

översätts till

(customers).GroupBy(c => c.Country, c => c.Name)

slutexempel

12.20.3.8 Transparenta identifierare

Vissa översättningar matar in intervallvariabler med transparenta identifierare som anges av *. Transparenta identifierare finns bara som ett mellanliggande steg i översättningsprocessen för frågeuttryck.

När en frågeöversättning matar in en transparent identifierare sprider ytterligare översättningssteg den transparenta identifieraren till anonyma funktioner och anonyma objektinitierare. I dessa sammanhang har transparenta identifierare följande beteende:

  • När en transparent identifierare inträffar som en parameter i en anonym funktion, är medlemmarna av den associerade anonyma typen automatiskt i omfånget i brödtexten i den anonyma funktionen.
  • När en medlem med en transparent identifierare finns i omfånget finns även medlemmarna i den medlemmen i omfånget.
  • När en transparent identifierare inträffar som en medlemsdeklarator i en anonym objektinitierare introduceras en medlem med en transparent identifierare.

I översättningsstegen som beskrivs ovan introduceras alltid transparenta identifierare tillsammans med anonyma typer, med avsikten att samla in flera intervallvariabler som medlemmar i ett enda objekt. En implementering av C# tillåts använda en annan mekanism än anonyma typer för att gruppera flera intervallvariabler. Följande översättningsexempel förutsätter att anonyma typer används och visar en möjlig översättning av transparenta identifierare.

Exempel: Exemplet

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.Total }

översätts till

from * in (customers).SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.Total }

som översätts ytterligare till

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(* => o.Total)
    .Select(\* => new { c.Name, o.Total })

som, när transparenta identifierare raderas, motsvarar

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(x => x.o.Total)
    .Select(x => new { x.c.Name, x.o.Total })

där x är en kompilatorgenererad identifierare som annars är osynlig och otillgänglig.

Exemplet

from c in customers
join o in orders on c.CustomerID equals o.CustomerID
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

översätts till

from * in (customers).Join(
    orders,
    c => c.CustomerID,
    o => o.CustomerID,
    (c, o) => new { c, o })
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

som ytterligare reduceras till

customers
    .Join(orders, c => c.CustomerID,
        o => o.CustomerID, (c, o) => new { c, o })
    .Join(details, * => o.OrderID, d => d.OrderID, (*, d) => new { *, d })
    .Join(products, * => d.ProductID, p => p.ProductID,
        (*, p) => new { c.Name, o.OrderDate, p.ProductName })

den slutliga översättningen av

customers
    .Join(orders, c => c.CustomerID,
        o => o.CustomerID, (c, o) => new { c, o })
    .Join(details, x => x.o.OrderID, d => d.OrderID, (x, d) => new { x, d })
    .Join(products, y => y.d.ProductID, p => p.ProductID,
        (y, p) => new { y.x.c.Name, y.x.o.OrderDate, p.ProductName })

där x och y är kompilatorgenererade identifierare som annars är osynliga och otillgängliga. slutexempel

12.20.4 Frågeuttrycksmönstret

Frågeuttrycksmönstret etablerar ett mönster med metoder som typer kan implementera för att stödja frågeuttryck.

En allmän typ C<T> stöder frågeuttrycksmönstret om dess offentliga medlemsmetoder och de offentligt tillgängliga tilläggsmetoderna kan ersättas av följande klassdefinition. Medlemmarna och tillgängliga extensonmetoder kallas för "form" av en generisk typ C<T>. En allmän typ används för att illustrera rätt relationer mellan parameter- och returtyper, men det är också möjligt att implementera mönstret för icke-generiska typer.

delegate R Func<T1,R>(T1 arg1);
delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);

class C
{
    public C<T> Cast<T>() { ... }
}

class C<T> : C
{
    public C<T> Where(Func<T,bool> predicate) { ... }
    public C<U> Select<U>(Func<T,U> selector) { ... }
    public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
        Func<T,U,V> resultSelector) { ... }
    public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,U,V> resultSelector) { ... }
    public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector) { ... }
    public O<T> OrderBy<K>(Func<T,K> keySelector) { ... }
    public O<T> OrderByDescending<K>(Func<T,K> keySelector) { ... }
    public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector) { ... }
    public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
        Func<T,E> elementSelector) { ... }
}

class O<T> : C<T>
{
    public O<T> ThenBy<K>(Func<T,K> keySelector) { ... }
    public O<T> ThenByDescending<K>(Func<T,K> keySelector) { ... }
}

class G<K,T> : C<T>
{
    public K Key { get; }
}

Metoderna ovan använder de allmänna ombudstyperna Func<T1, R> och Func<T1, T2, R>, men de kan lika gärna ha använt andra typer av ombud eller uttrycksträd med samma relationer i parameter- och returtyper.

Obs! Den rekommenderade relationen mellan C<T> och O<T> som säkerställer att ThenBy metoderna och ThenByDescending endast är tillgängliga på resultatet av en OrderBy eller OrderByDescending. slutkommentar

Obs! Den rekommenderade formen på resultatet av GroupBy– en sekvens med sekvenser, där varje inre sekvens har ytterligare en Key egenskap. slutkommentar

Obs! Eftersom frågeuttryck översätts till metodanrop med hjälp av en syntaktisk mappning har typerna stor flexibilitet i hur de implementerar något eller alla frågeuttrycksmönster. Till exempel kan metoderna i mönstret implementeras som instansmetoder eller som tilläggsmetoder eftersom de två har samma anropssyntax, och metoderna kan begära ombud eller uttrycksträd eftersom anonyma funktioner kan konverteras till båda. Typer som endast implementerar vissa av frågeuttrycksmönstret stöder endast översättningar av frågeuttryck som mappas till de metoder som typen stöder. slutkommentar

Obs! Namnområdet System.Linq tillhandahåller en implementering av frågeuttrycksmönstret för alla typer som implementerar System.Collections.Generic.IEnumerable<T> gränssnittet. slutkommentar

12.21 Tilldelningsoperatorer

12.21.1 Allmänt

Alla utom en av tilldelningsoperatorerna tilldelar ett nytt värde till en variabel, en egenskap, en händelse eller ett indexerarelement. Undantaget, = ref, tilldelar en variabelreferens (§9.5) till en referensvariabel (§9.7).

assignment
    : unary_expression assignment_operator expression
    ;

assignment_operator
    : '=' 'ref'? | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<='
    | right_shift_assignment
    ;

Den vänstra operanden för en tilldelning ska vara ett uttryck som klassificeras som en variabel, eller, med undantag för = ref, en egenskapsåtkomst, en indexerares åtkomst, en händelseåtkomst eller en tuppeln. Ett deklarationsuttryck tillåts inte direkt som en vänster operand, men kan inträffa som ett steg i utvärderingen av en dekonstruktionstilldelning.

Operatorn = kallas för enkel tilldelningsoperator. Den tilldelar värdet eller värdena för den högra operanden till variabeln, egenskapen, indexerelementet eller tuppelns element som anges av den vänstra operanden. Den enkla tilldelningsoperatörens vänstra operande får inte vara en händelseåtkomst (förutom enligt beskrivningen i §15.8.2). Den enkla tilldelningsoperatören beskrivs i §12.21.2.

Operatorn = ref kallas referenstilldelningsoperatorn. Det gör den högra operanden, som ska vara en variable_reference (§9.5), referenten av hänvisa till variabeln som designeras av den lämnade operanden. Referenstilldelningsoperatören beskrivs i §12.21.3.

Andra tilldelningsoperatorer än operatorerna och kallas för sammansatta tilldelningsoperatorer.= ref = Dessa operatorer utför den angivna åtgärden på de två operanderna och tilldelar sedan det resulterande värdet till variabeln, egenskapen eller indexeraren som anges av den vänstra operanden. De sammansatta tilldelningsoperatörerna beskrivs i §12.21.4.

Operatorerna += och med ett uttryck för händelseåtkomst som den vänstra operanden kallas för händelsetilldelningsoperatorer.-= Ingen annan tilldelningsoperator är giltig med en händelseåtkomst som den vänstra operanden. Händelsetilldelningsoperatörerna beskrivs i §12.21.5.

Tilldelningsoperatorerna är höger-associativa, vilket innebär att åtgärder grupperas från höger till vänster.

Exempel: Ett uttryck för formuläret a = b = c utvärderas som a = (b = c). slutexempel

12.21.2 Enkel tilldelning

Operatorn = kallas för enkel tilldelningsoperator.

Om den vänstra operanden i en enkel tilldelning är av formuläret E.P eller E[Ei] där E har kompileringstidstypen dynamic, är tilldelningen dynamiskt bunden (§12.3.3). I det här fallet är dynamictilldelningsuttryckets kompileringstidstyp , och den lösning som beskrivs nedan sker vid körning baserat på körningstypen Eför . Om den vänstra operanden är av det formulär E[Ei] där minst ett element av Ei har kompileringstidstypen dynamic, och kompileringstidstypen E inte är en matris, är den resulterande indexerarens åtkomst dynamiskt bunden, men med begränsad kompileringstidskontroll (§12.6.5).

En enkel tilldelning där den vänstra operanden klassificeras som en tuppeln kallas också för en dekonstruktionstilldelning. Om något av tuppelns element i den vänstra operanden har ett elementnamn uppstår ett kompileringsfel. Om något av tuppelns element i den vänstra operanden är en declaration_expression och något annat element inte är en declaration_expression eller en enkel ignorera, uppstår ett kompileringsfel.

Typen av en enkel tilldelning x = y är typen av en tilldelning till x av y, som rekursivt bestäms på följande sätt:

  • Om x är ett tupppeluttryck (x1, ..., xn), och y kan dekonstrueras till ett tupppeluttryck (y1, ..., yn) med n element (§12.7), och varje tilldelning till xi yi har typen Ti, har tilldelningen typen (T1, ..., Tn).
  • Annars, om x klassificeras som en variabel, är variabeln inte readonly, x har en typ Toch y har en implicit konvertering till T, så har tilldelningen typen T.
  • Annars, om x klassificeras som en implicit typvariabel (dvs. ett implicit skrivet deklarationsuttryck) och y har en typ T, är Tvariabelns härledda typ , och tilldelningen har typen T.
  • Annars, om x klassificeras som en egenskap eller indexerare åtkomst, egenskapen eller indexeraren har en tillgänglig uppsättning accessor, x har en typ T, och y har en implicit konvertering till T, då tilldelningen har typen T.
  • Annars är tilldelningen inte giltig och ett bindningstidsfel inträffar.

Körningsbearbetningen av en enkel tilldelning av formuläret x = y med typen T utförs som en tilldelning till x av y med typen T, som består av följande rekursiva steg:

  • x utvärderas om det inte redan var det.
  • Om x klassificeras som en variabel y utvärderas och konverteras vid behov till T genom en implicit konvertering (§10.2).
    • Om variabeln som anges av x är ett matriselement i en reference_type utförs en körningskontroll för att säkerställa att värdet som beräknas för y är kompatibelt med matrisinstansen som x är ett element. Kontrollen lyckas om y är null, eller om det finns en implicit referenskonvertering (§10.2.8) från den typ av instans som refereras av y till den faktiska elementtypen för matrisinstansen som innehåller x. Annars kastas en System.ArrayTypeMismatchException .
    • Värdet som är resultatet av utvärderingen och konverteringen av y lagras på den plats som anges av utvärderingen av xoch returneras som ett resultat av tilldelningen.
  • Om x klassificeras som en egenskap eller indexerares åtkomst:
    • y utvärderas och konverteras vid behov till T genom en implicit konvertering (§10.2).
    • Set-accessorn x för anropas med värdet som är resultatet av utvärderingen och konverteringen av y som värdeargument.
    • Värdet som är resultatet av utvärderingen och konverteringen av y returneras som ett resultat av tilldelningen.
  • Om x klassificeras som en tuppeln (x1, ..., xn) med adel n:
    • y dekonstrueras med n element till ett tupppeluttryck e.
    • en resultattupppel t skapas genom att e konvertera till T att använda en implicit tupppelkonvertering.
    • för varje xi i ordning från vänster till höger utförs en tilldelning till xi av t.Itemi , förutom att de xi inte utvärderas igen.
    • t returneras som ett resultat av tilldelningen.

Obs! Om kompileringstidstypen x är dynamic och det finns en implicit konvertering från kompileringstidstypen y till dynamickrävs ingen körningsmatchning. slutkommentar

Obs! Matrisens samavvikelseregler (§17.6) tillåter att ett värde av en matristyp A[] är en referens till en instans av en matristyp B[], förutsatt att det finns en implicit referenskonvertering från B till A. På grund av dessa regler kräver tilldelning till ett matriselement i en reference_type en körningskontroll för att säkerställa att värdet som tilldelas är kompatibelt med matrisinstansen. I exemplet

string[] sa = new string[10];
object[] oa = sa;
oa[0] = null;              // OK
oa[1] = "Hello";           // OK
oa[2] = new ArrayList();   // ArrayTypeMismatchException

den senaste tilldelningen gör att en System.ArrayTypeMismatchException genereras eftersom en referens till en ArrayList inte kan lagras i ett element i en string[].

slutkommentar

När en egenskap eller indexerare som deklareras i en struct_type är målet för en tilldelning, ska instansuttrycket som är associerat med egenskapen eller indexerarens åtkomst klassificeras som en variabel. Om instansuttrycket klassificeras som ett värde uppstår ett bindningstidsfel.

Obs! På grund av §12.8.7 gäller samma regel även för fält. slutkommentar

Exempel: Med tanke på deklarationerna:

struct Point
{
   int x, y;

   public Point(int x, int y)
   {
      this.x = x;
      this.y = y;
   }

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

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

struct Rectangle
{
    Point a, b;

    public Rectangle(Point a, Point b)
    {
        this.a = a;
        this.b = b;
    }

    public Point A
    {
        get { return a; }
        set { a = value; }
    }

    public Point B
    {
        get { return b; }
        set { b = value; }
    }
}

i exemplet

Point p = new Point();
p.X = 100;
p.Y = 100;
Rectangle r = new Rectangle();
r.A = new Point(10, 10);
r.B = p;

tilldelningarna till p.X, p.Y, r.Aoch r.B tillåts eftersom p och r är variabler. Men i exemplet

Rectangle r = new Rectangle();
r.A.X = 10;
r.A.Y = 10;
r.B.X = 100;
r.B.Y = 100;

tilldelningarna är alla ogiltiga, eftersom r.A och r.B inte är variabler.

slutexempel

12.21.3 Referenstilldelning

Operatorn = ref kallas referenstilldelningsoperatorn.

Den vänstra operanden ska vara ett uttryck som binder till en referensvariabel (§9.7), en referensparameter (förutom this), en utdataparameter eller en indataparameter. Den högra operanden ska vara ett uttryck som ger ett variable_reference som anger ett värde av samma typ som den vänstra operanden.

Det är ett kompileringstidsfel om ref-safe-context (§9.7.2) för den vänstra operanden är bredare än ref-safe-context för den högra operanden.

Rätt operande ska definitivt tilldelas vid tidpunkten för referenstilldelningen.

När den vänstra operanden binder till en utdataparameter är det ett fel om den utdataparametern inte har tilldelats definitivt i början av referenstilldelningsoperatorn.

Om den vänstra operanden är en skrivbar referens (dvs. den anger något annat än en ref readonly lokal parameter eller indataparameter) ska den högra operanden vara en skrivbar variable_reference. Om den högra operandvariabeln är skrivbar kan den vänstra operanden vara en skrivbar eller skrivskyddad referens.

Åtgärden gör den vänstra operanden till ett alias för variabeln höger operand. Aliaset kan göras skrivskyddat även om rätt operandvariabel är skrivbar.

Referenstilldelningsoperatorn ger en variable_reference av den tilldelade typen. Den kan skrivas om den vänstra operanden kan skrivas.

Referenstilldelningsoperatören får inte läsa den lagringsplats som anges av rätt operande.

Exempel: Här är några exempel på hur du använder = ref:

public static int M1() { ... }
public static ref int M2() { ... }
public static ref uint M2u() { ... }
public static ref readonly int M3() { ... }
public static void Test()
{
int v = 42;
ref int r1 = ref v; // OK, r1 refers to v, which has value 42
r1 = ref M1();      // Error; M1 returns a value, not a reference
r1 = ref M2();      // OK; makes an alias
r1 = ref M2u();     // Error; lhs and rhs have different types
r1 = ref M3();    // error; M3 returns a ref readonly, which r1 cannot honor
ref readonly int r2 = ref v; // OK; make readonly alias to ref
r2 = ref M2();      // OK; makes an alias, adding read-only protection
r2 = ref M3();      // OK; makes an alias and honors the read-only
r2 = ref (r1 = ref M2());  // OK; r1 is an alias to a writable variable,
              // r2 is an alias (with read-only access) to the same variable
}

slutexempel

Obs! När du läser kod med hjälp av en = ref operator kan det vara frestande att läsa ref delen som en del av operanden. Detta är särskilt förvirrande när operanden är ett villkorsuttryck ?: . När du till exempel läser ref int a = ref b ? ref x : ref y; är det viktigt att läsa detta som = ref operator och b ? ref x : ref y vara rätt operand: ref int a = ref (b ? ref x : ref y);. Det är viktigt att uttrycket ref b inte är en del av den instruktionen, även om det kan visas så vid första anblicken. slutkommentar

12.21.4 Sammansatt tilldelning

Om den vänstra operanden för en sammansatt tilldelning är av formuläret E.P eller E[Ei] där E har kompileringstidstypen dynamic, är tilldelningen dynamiskt bunden (§12.3.3). I det här fallet är dynamictilldelningsuttryckets kompileringstidstyp , och den lösning som beskrivs nedan sker vid körning baserat på körningstypen Eför . Om den vänstra operanden är av det formulär E[Ei] där minst ett element av Ei har kompileringstidstypen dynamic, och kompileringstidstypen E inte är en matris, är den resulterande indexerarens åtkomst dynamiskt bunden, men med begränsad kompileringstidskontroll (§12.6.5).

En åtgärd av formuläret x «op»= y bearbetas genom att tillämpa binär operatoröverbelastningsmatchning (§12.4.5) som om åtgärden skrevs x «op» y. Sedan kan du

  • Om returtypen för den valda operatorn implicit kan konverteras till typen av xutvärderas åtgärden som x = x «op» y, förutom att x endast utvärderas en gång.
  • Annars, om den valda operatorn är en fördefinierad operator, om returtypen för den valda operatorn uttryckligen kan konverteras till typen , x och om y är implicit konvertibel till typen av x eller operatorn är en skiftoperator, utvärderas åtgärden som x = (T)(x «op» y), där T är typen av x, förutom att x utvärderas endast en gång.
  • Annars är den sammansatta tilldelningen ogiltig och ett bindningstidsfel inträffar.

Termen "utvärderas bara en gång" innebär att i utvärderingen av x «op» ysparas x resultatet av eventuella konstituerande uttryck tillfälligt och återanvänds sedan när tilldelningen utförs till x.

Exempel: I tilldelningen A()[B()] += C(), där A är en metod som returnerar int[], och C B är metoder som returnerar int, anropas metoderna bara en gång, i ordningen A, B, C. slutexempel

När den vänstra operanden för en sammansatt tilldelning är en egenskapsåtkomst eller indexerareåtkomst, ska egenskapen eller indexeraren ha både en get-accessor och en uppsättningsåtkomst. Om så inte är fallet uppstår ett bindningstidsfel.

Den andra regeln ovan tillåter x «op»= y att utvärderas som x = (T)(x «op» y) i vissa sammanhang. Regeln finns så att de fördefinierade operatorerna kan användas som sammansatta operatorer när den vänstra operanden är av typen sbyte, byte, short, ushorteller char. Även om båda argumenten är av en av dessa typer ger de fördefinierade operatorerna ett resultat av typen int, enligt beskrivningen i §12.4.7.3. Utan en rollbesättning skulle det därför inte vara möjligt att tilldela resultatet till den vänstra operanden.

Den intuitiva effekten av regeln för fördefinierade operatorer är helt enkelt tillåten x «op»= y om både och x «op» y x = y tillåts.

Exempel: I följande kod

byte b = 0;
char ch = '\0';
int i = 0;
b += 1;           // OK
b += 1000;        // Error, b = 1000 not permitted
b += i;           // Error, b = i not permitted
b += (byte)i;     // OK
ch += 1;          // Error, ch = 1 not permitted
ch += (char)1;    // OK

Den intuitiva orsaken till varje fel är att en motsvarande enkel tilldelning också skulle ha varit ett fel.

slutexempel

Obs! Detta innebär också att sammansatta tilldelningsåtgärder stöder lyfta operatorer. Eftersom en sammansatt tilldelning x «op»= y utvärderas som antingen x = x «op» y eller x = (T)(x «op» y), omfattar utvärderingsreglerna implicit borttagna operatorer. slutkommentar

12.21.5 Händelsetilldelning

Om operatorns vänstra operand a += or -= klassificeras som en händelseåtkomst utvärderas uttrycket enligt följande:

  • Instansuttrycket, om det finns någon, av händelseåtkomsten utvärderas.
  • Den högra operanden för += operatorn eller -= utvärderas och konverteras vid behov till typen av vänster operande genom en implicit konvertering (§10.2).
  • En händelseåtkomst till händelsen anropas med en argumentlista som består av det värde som beräknades i föregående steg. Om operatorn var +=anropas tilläggsåtkomstorn. Om operatorn var -=anropas borttagningsåtkomstorn.

Ett uttryck för händelsetilldelning ger inget värde. Därför är ett uttryck för händelsetilldelning endast giltigt i samband med en statement_expression (§13.7).

12.22 Uttryck

Ett uttryck är antingen en non_assignment_expression eller en tilldelning.

expression
    : non_assignment_expression
    | assignment
    ;

non_assignment_expression
    : declaration_expression
    | conditional_expression
    | lambda_expression
    | query_expression
    ;

12.23 Konstanta uttryck

Ett konstant uttryck är ett uttryck som ska utvärderas fullständigt vid kompileringstid.

constant_expression
    : expression
    ;

Ett konstant uttryck ska antingen ha värdet null eller någon av följande typer:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, , float, double, decimal, bool, ; string
  • en uppräkningstyp. eller
  • ett standardvärdeuttryck (§12.8.21) för en referenstyp.

Endast följande konstruktioner tillåts i konstanta uttryck:

  • Literaler (inklusive literalen null ).
  • Referenser till const medlemmar i klass- och struct-typer.
  • Referenser till medlemmar i uppräkningstyper.
  • Referenser till lokala konstanter.
  • Parentesiserade underuttryck, som i sig är konstanta uttryck.
  • Gjutna uttryck.
  • checked och unchecked uttryck.
  • nameof Uttryck.
  • Fördefinierade +operatorerna , -, ! (logisk negation) och ~ unary.
  • Fördefinierade +operatorerna , , -*, /, %, <<, >>, &, , |, ^, &&, ||, ==, !=, <, >, , <=och >= binära operatorer.
  • Villkorsoperatorn ?: .
  • Operatorn ! null-forgiving (§12.8.9).
  • sizeof uttryck, förutsatt att den ohanterade typen är en av de typer som anges i §23.6.9 för vilka sizeof returnerar ett konstant värde.
  • Standardvärdeuttryck, förutsatt att typen är en av de typer som anges ovan.

Följande konverteringar tillåts i konstanta uttryck:

  • Identitetskonverteringar
  • Numeriska konverteringar
  • Uppräkningskonverteringar
  • Konverteringar av konstanta uttryck
  • Implicita och explicita referenskonverteringar, förutsatt att källan till konverteringarna är ett konstant uttryck som utvärderas till null värdet.

Obs! Andra konverteringar, inklusive boxning, unboxing och implicita referenskonverteringar av icke-värdennull tillåts inte i konstanta uttryck. slutkommentar

Exempel: I följande kod

class C
{
    const object i = 5;         // error: boxing conversion not permitted
    const object str = "hello"; // error: implicit reference conversion
}

initieringen av i är ett fel eftersom en boxningskonvertering krävs. Initieringen av str är ett fel eftersom en implicit referenskonvertering från ett icke-värdenull krävs.

slutexempel

När ett uttryck uppfyller kraven som anges ovan utvärderas uttrycket vid kompileringstid. Detta gäller även om uttrycket är ett underuttryck av ett större uttryck som innehåller icke-konstanta konstruktioner.

Kompileringstidsutvärderingen av konstanta uttryck använder samma regler som körningsutvärdering av icke-konstanta uttryck, förutom att när körningsutvärderingen skulle ha genererat ett undantag orsakar kompileringstidsutvärdering ett kompileringsfel.

Om inte ett konstant uttryck uttryckligen placeras i en unchecked kontext orsakar spill som inträffar i aritmetiska åtgärder av integraltyp och konverteringar under kompileringstidsutvärderingen av uttrycket alltid kompileringsfel (§12.8.20).

Konstanta uttryck krävs i de kontexter som anges nedan och detta anges i grammatiken med hjälp av constant_expression. I dessa sammanhang uppstår ett kompileringsfel om ett uttryck inte kan utvärderas fullständigt vid kompilering.

  • Konstanta deklarationer (§15.4)
  • Uppräkningsmedlemsdeklarationer (§19.4)
  • Standardargument för parameterlistor (§15.6.2)
  • case etiketter för en switch instruktion (§13.8.3).
  • goto case instruktioner (§13.10.4)
  • Dimensionslängder i ett uttryck för matrisskapande (§12.8.17.5) som innehåller en initialiserare.
  • Attribut (§22)
  • I en constant_pattern (§11.2.3)

En implicit konstant uttryckskonvertering (§10.2.11) tillåter att ett konstant uttryck av typen int konverteras till sbyte, byte, short, ushort, uinteller ulong, förutsatt att värdet för konstantuttrycket ligger inom måltypens intervall.

12.24 Booleska uttryck

En boolean_expression är ett uttryck som ger ett resultat av typen bool, antingen direkt eller via tillämpning av operator true i vissa kontexter som anges i följande:

boolean_expression
    : expression
    ;

Det kontrollerande villkorsuttrycket för en if_statement (§13.8.2), while_statement (§13.9.2), do_statement (§13.9.3) eller for_statement (§13.9.4) är en boolean_expression. Operatorns ?: kontrollerande villkorsuttryck (§12.18) följer samma regler som en boolean_expression, men av operatorprioritetsskäl klassificeras som en null_coalescing_expression.

En boolean_expressionE krävs för att kunna skapa ett värde av typen bool, enligt följande:

  • Om E implicit konverteras till bool tillämpas den implicita konverteringen vid körning.
  • Annars används unary operator overload resolution (§12.4.4) för att hitta en unik bästa implementering av operator trueE, och den implementeringen tillämpas vid körning.
  • Om ingen sådan operator hittas uppstår ett bindningstidsfel.