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 typad variabel har ingen associerad typ.
  • En null-literal. 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 tupl. Varje tuppel 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 åtkomstmetod för en instansegenskap anropas, blir resultatet av att utvärdera instansuttrycket den instans som representeras av this (§12.8.14).
  • Åtkomst för indexering. Varje indexerares åtkomst har en associerad typ, nämligen indexerarens elementtyp. Dessutom har en indexerare ett associerat instansuttryck och en associerad argumentlista. När en accessor av en indexeråtkomst anropas blir resultatet av utvärderingen av instansuttrycket den instans som representeras av this (§12.8.14), och resultatet av utvärderingen av argumentlistan utgör parameterlistan för anropet.
  • Ingenting. Detta inträffar när uttrycket är ett anrop av en metod med en returtyp av 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 endast 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 endast 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 som härrör från en medlemssökning (§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.
  • Tillgång till ett evenemang 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 +=- och -=-operatörerna (§12.21.5). I andra sammanhang orsakar ett uttryck som klassificeras som en händelseåtkomst ett kompileringsfel. När en åtkomstmetod för 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 i flera kontexter för att utlösa ett undantag i ett uttryck. Ett throw-uttryck kan konverteras genom en implicit konvertering till valfri typ.

En egenskapsåtkomst eller indexeråtkomst omklassificeras alltid som ett värde genom att utföra ett anrop av get-accessorn eller set-accessorn. Den specifika accessorn bestäms av kontexten för egenskapen eller indexerarens åtkomst: Om åtkomsten är målet för en tilldelning anropas den inställda 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, en händelseåtkomst eller en indexerartillgång på en instans.

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 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 associerad med indexerarens åtkomstuttryck, och resultatet av anropet blir värdet för indexerarens åtkomstuttryck.
  • Värdet för ett tuppeluttryck erhålls genom att tillämpa en implicit tuppelkonvertering (§10.2.13) på typen av tuppeluttrycket. 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 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 vid kompileringstillfället om ett uttryck innehåller ett fel. Den här metoden kallas statisk bindning.

Men om ett uttryck är ett dynamiskt uttryck (d.v.s. 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 vid kompileringstillfället. Om körningsbindningen misslyckas, rapporteras fel som undantag under 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), ~, ++, --, true, false
  • Ö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 underrubriker refererar termen bindningstid antingen till kompileringstid eller körningstid, beroende på när bindningen sker.

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: överbelastningen 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örtid.

slutexempel

12.3.3 Dynamisk bindning

Det här underavsnittet är informativt.

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 implementeringsdefinierat – implementeras av dynamiska objekt för att signalera till C#-körmiljön att de har särskild 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 kompileringstidstypen 'dynamic' anses ha samma typ som det faktiska värdet som uttrycket utvärderas till vid körningstid.
  • 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:

  • Unära operatorer. De unära operatorerna tar en operand och använder antingen prefixnotation (till exempel –x) eller postfixnotation (till exempel x++).
  • Binära operatorer. De binära operatorerna tar två operander och alla använder infix-notation (till exempel x + y).
  • Ternär operator. Det finns bara en ternär operator, ?:, och den tar tre operander och använder infixnotation (c ? x : y).

Ordningen för utvärdering av operatorer i ett uttryck bestäms av operatorernas prioritet och associativitet av operatörerna (§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 för i. Metoden G anropas med det gamla värdet ioch 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 specificeras för operationer där en eller båda operanderna är av en användardefinierad klass eller strukturtyp.

12.4.2 Operatorprioritet och associativitet

När ett uttryck innehåller flera operatorer, styr företrädet hos operatorerna ordningen i vilken 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 operatorn för binär +. slutkommentar

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

Obs: En additive_expression består till exempel av en sekvens med multiplicative_expressionavgränsade med operatorerna + eller -, vilket ger operatorerna + och - lägre prioritet än operatorerna *, /och %. slutkommentar

Obs: I följande tabell sammanfattas alla operatorer i prioritetsordning från högsta till lägsta:

Delavsnitt 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 Multiplikativ * / %
§12.10 Tillsats + -
§12.11 Skifta << >>
§12.12 Relations- och typtestning < > <= >= is as
§12.12 Jämlikhet == !=
§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 Villkorlig ?:
§12.21 och §12.19 Tilldelnings- och lambda-uttryck = = ref *= /= %= += -= <<= >>= &= ^= \|= =>

slutkommentar

När en operand inträffar mellan två operatorer med samma prioritet styr associativitet för operatorerna i vilken ordning å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 först y med z och lägger sedan till resultatet i x, men (x + y) * z lägger först till x och y och multiplicerar sedan resultatet med z. slutexempel

12.4.3 Operator-överladdning

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 unära operatorerna är:

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

Observera: Ä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

Notera: Den null-förlåtande operatorn (postfix !, §12.8.9) är inte en överbelastbar 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 =, &&, ||, ??, ?:, =>, checked, unchecked, new, typeof, default, asoch is operatorer.

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

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

Själva tilldelningsoperatorn (=) kan inte överbelastas. En tilldelning utför alltid en enkel lagring av ett värde i en variabel (§12.21.2).

Typomvandlingsoperationer, såsom (T)x, överlagras genom att tillhandahålla användardefinierade konverteringar (§10.5).

Obs: Användardefinierade konverteringar påverkar inte beteendet för operatorerna is eller as. slutkommentar

Elementåtkomst, till exempel a[x], anses inte vara en överbelastningsbar 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 notationer för unära och binära operatorer. I den första posten anger «op» alla överlagbara unary prefixoperatorer. I det andra inlägget anger «op» de unära postfixoperatorerna ++ och --. I den tredje posten anger «op» alla överlagrbara binära operatorer.

Obs: För ett exempel på överladdning av ++ och -- operatorer, se §15.10.2. slutkommentar

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.

Note: 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.2och är alltid vänster-associativ. slutexempel

Obs: Även om det är möjligt för en användardefinierad operator att utföra alla beräkningar som den önskar, avråds starkt från implementeringar som ger andra resultat än de som intuitivt förväntas. Till exempel bör en implementering av operatorn == 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 genom §12.21 ange fördefinierade implementeringar av operatörerna och eventuella ytterligare regler som gäller för varje operatör. Beskrivningarna använder termerna unärt operator-överlastningslösning, binärt operator-överlastningslösning, numerisk befordranoch lyfta operatordefinitioner som finns i följande avsnitt.

12.4.4 Lösning för överbelastning av en unär operator

En operation av formen «op» x eller x «op», där «op» är en överlagringsbar unär operator, och x är ett uttryck av typen X, bearbetas på följande sätt:

  • Den uppsättning användardefinierade operatorer som tillhandahålls av X för å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. I annat fall blir de fördefinierade binära operator «op»-implementeringarna, inklusive deras hissade former, de kandidatoperatorer som ska användas för operationen. 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 den här operatorn blir resultatet av överlagringsmatchningsprocessen. 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 Xoch y är ett uttryck av typen Y, bearbetas på följande sätt:

  • Uppsättningen med användardefinierade operatorer som tillhandahålls av X och Y för åtgärden operator «op»(x, y) bestäms. Uppsättningen består av föreningen av de kandidatoperatörer som tillhandahålls av X och de kandidatoperatörer som tillhandahålls av Y, vilka 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, uppstår endast delade kandidatoperatorer i den kombinerade uppsättningen en gång.
    • Om det finns en identitetskonvertering mellan X och Yoch en operator «op»Y som tillhandahålls av Y har samma returtyp som en «op»X som tillhandahålls av X samt operandtyperna av «op»Y har en identitetskonvertering till motsvarande operandtyper av «op»X, så 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. I annat fall blir de fördefinierade binära operator «op»-implementeringarna, inklusive deras hissade former, de kandidatoperatorer som ska användas för operationen. 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 den här operatorn blir resultatet av överlagringsmatchningsprocessen. Om det inte går att välja en enda bästa operator uppstår ett bindningstidsfel.

12.4.6 Kandidatanvändardefinierade operatorer

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 för operatorn «op»(A) enligt följande:

  • Fastställ typen T₀. Om T är en nullbar värdetyp är T₀ dess underliggande typ. annars är T₀ 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 kandidatoperatörer av alla sådana tillämpliga operatörer i T₀.
  • Om T₀ är objectär annars 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 för T₀eller den effektiva basklassen för T₀ om T₀ är en typparameter.

12.4.7 Numeriska kampanjer

12.4.7.1 Allmänt

Det här underavsnittet är informativt.

§12.4.7 och dess underrubriker ä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), relationella (§12.12), och integral logiska (§12.13.2) operatorer.

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 promotion är inte en distinkt mekanism, utan snarare en effekt av att tillämpa överbelastningsresolution på de fördefinierade operatorerna. Specifikt påverkar numerisk befordran inte utvärderingen av användardefinierade operatorer, även om användardefinierade operatorer kan implementeras för att ge 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å denna uppsättning 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 överlagringsmatchning 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. När det gäller operationen i * d, där i är en int och d är en double, väljer overload resolution operator *(double, double) som den bästa operatören. slutexempel

Slut på informativ text.

12.4.7.2 Unära numeriska främjanden

Det här underavsnittet är informativt.

Unär numerisk befordran sker för operander av de fördefinierade +, -och ~ unära operatorerna. Unary numerisk befordran består helt enkelt av att konvertera operander av typen sbyte, byte, short, ushorteller char till typen int. Dessutom, för unär – operatorn konverterar unär numerisk promotion operander av typen uint till typen long.

Slut på informativ text.

12.4.7.3 Binära numeriska promotioner

Det här underavsnittet är informativt.

Binär numerisk befordran sker för operanderna i den fördefinierade +, -, *, /, %, &, |, ^, ==, !=, >, <, >=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 antingen operand är av typen decimalkonverteras den andra operanden till typen decimaleller ett bindningstidsfel inträffar om den andra operanden är av typen float eller double.
  • Om någon av operanderna är av typen doublekonverteras annars den andra operanden till typen double.
  • Om någon av operanderna är av typen floatkonverteras annars den andra operanden till typen float.
  • Om annars någon av operanderna är av typen ulong, konverteras den andra operanden till typen ulong, eller ett bindningstidsfel uppstår om den andra operanden är av type sbyte, short, inteller long.
  • Om någon av operanderna är av typen longkonverteras annars den andra operanden till typen long.
  • Om antingen operand är av typen uint och den andra operanden är av typen sbyte, shorteller intkonverteras båda operanderna till typen long.
  • Om någon av operanderna är av typen uintkonverteras annars den andra operanden till typen uint.
  • I annat fall konverteras båda operanderna till typen int.

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

Obs: Observera också att det inte är möjligt för en operande 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 samt 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

Lifted-operatorer tillåter att fördefinierade och användardefinierade operatorer som körs på icke-nullbara värdetyper också kan 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 oföränderliga operatorerna +, ++, -, --, !(logisk negation) och ~finns en upplyft 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 lyftande operatorn genererar ett null värde om operanden ä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 det en hävd form av en operator om operand- och resultattyperna alla är icke-nullbara värdetyper. Det upplyfta formuläret konstrueras genom att en enda ?-modifier läggs till varje operand- och resultattyp. Den lyftade operatorn genererar ett null-värde om en eller båda operanderna är null (ett undantag är &- och |-operatorerna av bool?-typ, som beskrivs i §12.13.5). Annars packar den lyftade operatorn upp operanderna, tillämpar den underliggande operatorn och omsluter resultatet.
  • För likhetsoperatorerna == och !=finns det en upplyft form av en operator om operandtyperna båda är icke-nullbara värdetyper och om resultattypen är bool. Det upplyfta formuläret skapas genom att en enda "?"-modifierare läggs till varje operandtyp. Den lyfta operatorn anser att två null-värden är lika, och att ett null-värde inte är lika med något icke-null värde. Om båda operanderna inte ärnullskriver den lyftande operatorn upp operanderna och tillämpar den underliggande operatorn för att skapa bool resultat.
  • För relationsoperatorerna <, >, <=och >=finns det en upplyft form av en operator om operandtyperna båda är icke-nullbara värdetyper och om resultattypen är bool. Det upplyfta formuläret skapas genom att en enda "?"-modifierare läggs till varje operandtyp. Den upphöjda operatorn genererar värdet false om en eller båda av operanderna är null. Annars packar den lyftade operatorn upp operanderna och tillämpar den underliggande operatorn för att skapa ett bool-resultat.

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 förekommer som primary_expression av en invocation_expression (§12.8.10.2), sägs medlemmen vara anropad.

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

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 argument av K typ i en typ T bearbetas på följande sätt:

  • Först fastställs en uppsättning tillgängliga medlemmar med namnet N:
    • Om T är en typparameter är uppsättningen en union av uppsättningar med tillgängliga medlemmar med namnet 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 tillgängliga medlemmar med namnet N i object.
    • Annars består uppsättningen av alla tillgängliga (§7.5) medlemmar med namnet N i T, inklusive ärvda medlemmar och de tillgängliga medlemmarna med namnet 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 har en override-modifiering exkluderas från uppsättningen.
  • Om K ä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 deklarerade i en bastyp av S tas bort från uppsättningen.
    • Om M är en metod tas alla icke-metodmedlemmar som deklarerats i en bastyp av S bort 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 deklareras 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 som 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 enkelarvsökningar är aldrig tvetydiga. De tvetydigheter som kan uppstå vid medlemssökningar i gränssnitt med flera arv 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 T ingen bastyp.
  • Om T är en enum_typeär bastyperna för T klasstyperna System.Enum, System.ValueTypeoch object.
  • Om T är en struct_typeär bastyperna för T klasstyperna System.ValueType och object.

    Anmärkning: En nullable_value_type är en struct_type (§8.3.1). slutkommentar

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

12.6 Funktionsmedlemmar

12.6.1 Allmänt

Funktionella medlemmar ä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
  • Evenemang
  • Indexerare
  • Användardefinierade operatorer
  • 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 ett funktionsmedlemsanrop beror på den specifika funktionsmedlemstypen.

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 överlagringsupplösning, beskrivs den faktiska exekveringsprocessen för att anropa funktionsmedlemmen i §12.6.6.

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

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 är staticär instansuttrycket this.
T.F(x, y) Överbelastningsupplösning används för att välja den bästa metoden F i en klass eller en struktur T. Ett bindningstidsfel uppstår om metoden inte är static. Metoden anropas med argumentlistan (x, y).
e.F(x, y) Överbelastningsupplö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 strukturen anropas. Ett kompileringsfel uppstår om P är skrivskyddad. Om P inte är staticär instansuttrycket this.
P = value Set-accessorn för egenskapen P i den innehållande klassen eller struct anropas med argumentlistan (value). Ett kompileringsfel uppstår om P är skrivskyddad. Om P inte är staticär instansuttrycket this.
T.P Hämtaren för egenskapen P i klassen eller strukturen T anropas. Ett kompileringsfel uppstår om P inte är static eller om P är skrivskyddad.
T.P = value Set-accessorn för egenskapen P i klassen eller struct-T anropas med argumentlistan (value). Ett kompileringsfel uppstår om P inte är static eller om P är skrivskyddad.
e.P Get-accessorn för egenskapen P i klassen, strukturen eller gränssnittet som anges av typen E anropas med instansuttrycket e. Ett bindningstidsfel uppstår om P är static eller om P är skrivskyddad.
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 kompileringstidfel 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 strukturen anropas. Om E inte är staticär instansuttrycket this.
E -= value Ta bort-accessorn för händelsen E i den innehållande klassen eller structen anropas. Om E inte är staticär instansuttrycket this.
T.E += value Add-åtkomsten för händelsen E i klassen eller strukturen T anropas. Ett bindningstidsfel uppstår om E inte är static.
T.E -= value Remove-accessorn för händelsen E i klassen eller strukturen T anropas. Ett bindningstidsfel uppstår om E inte är static.
e.E += value Tilläggsåtkomsten för händelsen E i klassen, strukturen eller gränssnittet som ges av typen E anropas med instansuttrycket e. Ett bindningstidsfel uppstår om E är static.
e.E -= value Ta bort-accessorn för händelsen E i klassen, strukturen eller gränssnittet som anges av typen E anropas med instansuttrycket e. Ett bindningstidsfel uppstår om E är static.
Indexerarens åtkomst e[x, y] Överbelastningsupplösning 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 Överbelastningsupplösning används för att välja den bästa indexeraren i klassen, struct eller gränssnittet som anges av typen e. Indexerarens set-tillgångsmetod 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 unära operatorn i klassen eller strukturen som anges av typen x. Den valda operatorn anropas med argumentlistan (x).
x + y Överbelastningslösning används för att välja den bästa binära operatorn i klasserna eller strukturerna som ges av typerna av x och y. Den valda operatorn anropas med argumentlistan (x, y).
Instanskonstruktoranrop new T(x, y) Överbelastningsupplösning används för att välja den bästa instanskonstruktorn i en klass eller struktur 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, när du anropar set-funktionen, innehåller argumentlistan dessutom det uttryck som anges som den högra operand för tilldelningsoperatorn.

    Obs: Det här ytterligare argumentet används inte för överlagringslösning, bara under anrop av den angivna åtkomstmetoden. 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 +=- eller -= operatorn.
  • 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 delegeringsanrop specificeras 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 arguments, 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 för 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 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äge för argumentet: värde, indata, referenseller 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.
    • När du anropar tilldelningsåtkomstgivaren för indexerare, motsvarar uttrycket som anges som den högra operanden för tilldelningsoperatorn den implicita parameter value i tilldelningsåtkomstdeklarationen.
  • 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 kommer efter ett namngivet argument som inte står på rätt plats eller ett namngivet argument som motsvarar en parameterfält.

    Note: Detta förhindrar att void M(bool a = true, bool b = true, bool c = true); 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örningsprocessen 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 inställt på att vara 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: Givet 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 M1(i)-metodanropet skickas själva i som ett indataargument, eftersom det klassificeras som en variabel och har samma typ int som indataparametern. I M1(i + 5)-metodanropet 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_typeutfö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 gör att en System.ArrayTypeMismatchException kastas eftersom den faktiska elementtypen av 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å vilken som är tillämplig (§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 kan konverteras (§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 elementtypen för parametermatrisen. I det här fallet skapar anropet en instans av parametermatristypen med en längd som motsvarar antalet argument, initierar elementen i matrisinstansen med de angivna argumentvärdena och använder den nyligen skapade matrisinstansen som det faktiska argumentet.

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

Exempel: Således, 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 dess expanderade form 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 denna deklaration

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.)

Note: Eftersom dessa alltid är konstanta påverkar deras utvärdering inte de återstående argumenten. 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 typinferens process att 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 överbelastningsupplösningssteget 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 överbelastningslösning väljer en generisk metod att anropa, används de infererade typargumenten 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 i 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 fastställs varje typparameter Xᵢ antingen till en viss typ Sᵢ eller förblir inte fastställda med en associerad uppsättning begränsningar. Var och en av begränsningarna är av någon typ T. Initialt ä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 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 parametertypsinferens (§12.6.3.8) frånEᵢtillTᵢ
  • Om Eᵢ annars har 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ånUtillTᵢ.
  • I annat fall om Eᵢ har 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ånUtillTᵢ.
  • I annat fall 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ånUtillTᵢ.
  • Om Eᵢ annars har en typ U och motsvarande parameter är en indataparameter (§15.6.2.3.2) görs en lägre bindningsslutsats (§12.6.3.10) frånUtillTᵢ.
  • 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 ofixerade typvariabler Xᵢ som inte är beroende av (§12.6.3.6) alla Xₑ är fasta (§12.6.3.12).
  • Om det inte finns några sådana typvariabler, är alla ofixerade typvariabler Xᵢfixerade för vilka alla följande villkor gäller:
    • Det finns minst en typvariabel Xₑ som beror påXᵢ
    • Xᵢ har en uppsättning gränser som inte är tomma
  • 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 de utdatatyperna (§12.6.3.5) innehåller ofixerade typvariabler Xₑ men de indatatyperna (§12.6.3.4) gör annars inte det, en inferens (§12.6.3.7) görs frånEᵢtillTᵢ. Sedan upprepas den andra fasen.

12.6.3.4 Indatatyper

Om E är en metodgrupp eller en implicit typ av anonym funktion och T är en delegattyp eller uttrycksträdstyp är alla parametertyper av Tindatatyper avEmed typenT.

12.6.3.5 Utdatatyper

Om E är en metodgrupp eller en anonym funktion och T är en delegat eller uttrycksträdtyp är returtypen för T en utdatatyp avEmed typenT.

12.6.3.6 Beroende

En typvariabel som Xᵢär direkt beroende av en typvariabel Xₑ om det för vissa argument Eᵥ med typ TᵥXₑ 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ₑär direkt beroende avXᵢ eller om Xᵢär direkt beroende avXᵥ och Xᵥär beroende avXₑ. Därför ä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 Etill 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ₓ, då görs en underordnad inferens (§12.6.3.10) frånUtillTₓ.
  • Om E är en metodgrupp och T är en ombudstyp eller uttrycksträdstyp med parametertyper T₁...Tᵥ och returtyp Tₓ, och överlagringslösning för E med typerna T₁...Tᵥ ger en enda metod med returtyp U, görs en lägre gränsinferens frånUtillTₓ.
  • Om E annars är ett uttryck med typen Ugörs en lägre slutsatsdragningfrånUtillT.
  • Annars görs inga slutsatsdragningar.

12.6.3.8 Explicita parametertypinferenser

En explicit parametertypsinferens görs från ett uttryck Eför att 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 delegattyp eller uttrycksträdstyp med parametertyper V₁...Vᵥ görs för varje Uᵢ en exakt slutsatsdragning (§12.6.3.9) frånUᵢtill motsvarande Vᵢ.

12.6.3.9 Exakta slutsatsdragningar

En exakt slutsatsdragningfrån en typ Utill en typ V görs på följande sätt:

  • Om V är en av de ofixeradeXᵢ, läggs U till i uppsättningen av exakta gränser för Xᵢ.
  • Annars bestäms uppsättningar V₁...Vₑ och U₁...Uₑ genom att kontrollera om något av följande fall gäller:
    • V är en matristyp V₁[...] och U är en matristyp U₁[...] av 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 varje Uᵢ till motsvarande Vᵢ.
  • Annars görs inga slutsatsdragningar.

12.6.3.10 Lägre bundna slutsatsdragningar

En lägre bunden slutsats från en typ Utill en typ V görs på följande sätt:

  • Om V är en av de ofixeradeXᵢ, läggs U till i uppsättningen av lägre gränser för Xᵢ.
  • Annars, om V är typen V₁? och U är typen U₁?, görs en lägre gränsinferens från U₁ till V₁.
  • Annars bestäms uppsättningar U₁...Uₑ och V₁...Vₑ genom att kontrollera om något av följande fall gäller:
    • V är en matristyp V₁[...]och U är en matristyp U₁[...]av samma rangordning
    • V är en av IEnumerable<V₁>, ICollection<V₁>, IReadOnlyList<V₁>>, IReadOnlyCollection<V₁> eller 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ₑ> så att 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 det fall grä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 varje Uᵢ till motsvarande Vᵢ enligt följande:
    • Om Uᵢ inte är känd för att vara en referenstyp görs en exakt slutsatsdragning
    • Annars, om U är en matristyp, görs en inferens för nedre gräns .
    • Om V är C<V₁...Vₑ> beror annars slutsatsdragningen på parametern i-th typ av C:
      • Om det är covariant görs en nedre gräns-inferens .
      • Om den är kontravariant skapas en övre gränsinferens.
      • Om den är invariant görs en exakt slutsatsdragning.
  • Annars görs inga slutsatsdragningar.

12.6.3.11 Övre bundna slutsatsdragningar

En övergripande slutsats från en typ Utill en typ V görs på följande sätt:

  • Om V är en av de ofixeradeXᵢ läggs U till i uppsättningen av övre gränser för Xᵢ.
  • Annars bestäms uppsättningar V₁...Vₑ och U₁...Uₑ genom att kontrollera om något av följande fall gäller:
    • U är en matristyp U₁[...]och V är en matristyp V₁[...]av samma rangordning
    • U är en av IEnumerable<Uₑ>, ICollection<Uₑ>, IReadOnlyList<Uₑ>, IReadOnlyCollection<Uₑ> eller IList<Uₑ> och V är en endimensionell matristyp Vₑ[]
    • U är typen U1? och V är typen V1?
    • U är en C<U₁...Uₑ>Vclass, struct, interface eller delegate typ som identical till, inherits från (direkt eller indirekt) eller implementerar (direkt eller indirekt) en unik typ C<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 vid slutsatsdragning 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 varje Uᵢ till motsvarande Vᵢ enligt följande:
    • Om Uᵢ inte är känd för att vara en referenstyp görs en exakt slutsatsdragning
    • Om V är en matristyp görs annars en övre gränsinferens
    • Om U är C<U₁...Uₑ> beror annars slutsatsdragningen på parametern i-th typ av C:
      • Om den är covariant skapas en övre gränsinferens.
      • Om det är kontravariant görs en undre gräns inferens.
      • Om den är invariant görs en exakt slutsatsdragning.
  • Annars görs inga slutsatsdragningar.

12.6.3.12 Lösning

En variabel av typen Xᵢ med en uppsättning gränser är fast på följande sätt:

  • Uppsättningen av kandidattyperUₑ börjar som alla typer inom gränserna för Xᵢ.
  • Varje bindning till Xᵢ granskas i sin tur: För varje exakt bunden U av Xᵢ tas alla typer Uₑ som inte är identiska med U bort från kandidatuppsättningen. För varje lägre bunden U av Xᵢ alla typer Uₑ som det finns inte en implicit konvertering från U tas bort från kandidatuppsättningen. För varje övre gräns U av Xᵢ alla typer Uₑ från vilka det finns inte en implicit konvertering till U tas 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, är Xᵢ fast till V.
  • Annars misslyckas typinferensen.

12.6.3.13 Uppskjuten returtyp

Den härledda returtypen för en anonym funktion F används under typinferens och överbelastningsmatchning. 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ärledda effektiva returtypen bestäms på följande sätt:

  • Om innehållet i F är ett -uttryck som har en typ, är den härledda effektiva returtypen för F samma som typen för det uttrycket.
  • Om kroppen av F är ett block och uppsättningen av uttrycken i instruktionerna i blocket return har en bästa gemensamma typ T (§12.6.3.15), då är den härledda effektiva returtypen för FT.
  • Annars kan en effektiv returtyp inte härledas för F.

Den härledda returtypen bestäms på följande sätt:

  • Om F är asynkron och brödtexten i F antingen är ett uttryck som klassificeras som ingenting (§12.2), eller ett block där inga return-uttryck har uttryck, är den härledda returtypen «TaskType» (§15.15.1).
  • Om F är asynkron och har en härledd effektiv returtyp Tär den härledda returtypen «TaskType»<T>»(§15.15.1).
  • Om F inte är asynkron och har en härledd effektiv returtyp Tär den härledda returtypen T.
  • 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 Select tilläggsmetod som deklarerats i klassen System.Linq.Enumerable:

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);
            }
        }
   }
}

Om System.Linq namnområdet importerades med ett using namespace-direktiv och gav en klass Customer med en Name egenskap av typen stringkan metoden Select 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 utökningsmetod (§12.8.10.3) av Select bearbetas genom att anropet omskrivs till ett statiskt 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 kundens argument relaterat till källparametern, vilket härleder TSource vara Customer. Med hjälp av inferensprocessen för anonym funktionstyp som beskrivs ovan får c typ Customer, och uttrycket c.Name är relaterat till returtypen för väljareparametern, vilket härleder TResult vara string. Anropet är därmed likvärdigt med

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. Givet 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);
    }
}

Typ-inferens för anropet fortsätter enligt följande: Först kopplas argumentet "1:15:30" till värdeparametern, vilket härleder X att vara string. Sedan får parametern för den första anonyma funktionen, s, den härledda typen string, och uttrycket TimeSpan.Parse(s) är relaterat till returtypen för f1, och härleder Y att vara System.TimeSpan. Slutligen får parametern för den andra anonyma funktionen, t, den härledda typen System.TimeSpan, och uttrycket t.TotalHours är relaterat till returtypen för f2, och härleder Z att vara double. 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 metodgruppen M tilldelas till ombudstypen D uppgiften med typinferens är 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 argument typer, inget argument uttryck. I synnerhet finns det inga anonyma funktioner och därför inget behov av flera inferensfaser.

I stället betraktas alla Xᵢ som ofixerade, och en med lägre bindning görs från varje argumenttyp Uₑ av Dför att motsvarande parametertyp Tₑ för M. Om inga gränser hittades för någon av Xᵢ misslyckas typinferensen. Annars är alla Xᵢfastställda 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 hittas elementtyperna för implicit typade matriser och returtyperna av anonyma funktioner med blockkroppar på detta sätt.

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

  • En ny typvariabel X introduceras.
  • För varje uttryck utförs Ei en inferens (§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 vanligaste typen.
  • Annars misslyckas slutsatsdragningen.

Note: Intuitivt motsvarar den här slutsatsen 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 som nämns i en invocation_expression (§12.8.10).
  • Anrop av en instanskonstruktor som namnges i en object_creation_expression (§12.8.17.2).
  • Anrop av en indexerartillgång via en element_access (§12.8.12).
  • Anrop av en fördefinierad eller användardefinierad operator som anges 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 med å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 utses den bästa funktionsmedlemmen bland de tillämpliga kandidatfunktionsmedlemmarna. Om uppsättningen endast innehåller en funktionsmedlem, är den funktionsmedlemmen den bästa. 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 underklasuler definierar de exakta betydelserna av termerna tillämplig 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 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 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 modifierare, finns det en identitetskonvertering mellan typen av argumentuttrycket (om det finns) 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 dess normala formulär. 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 expanderade formulär:

  • 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 något av följande gäller för varje argument i A:
    • 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överföringsläget för argumentet är värde och parameteröverföringsläget för motsvarande parameter är inmatning, 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: Givet 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 ett simple_name eller en member_access via en typ.
  • En instansmetod är endast tillämplig 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 i den ordning de visas i den ursprungliga argumentlistan och utelämnar alla out 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.

Med tanke på en argumentlista A med en uppsättning argumentuttryck {E₁, E₂, ..., Eᵥ} och två tillämpliga funktionsmedlemmar Mᵥ och Mₓ med parametertyper {P₁, P₂, ..., Pᵥ} och {Q₁, Q₂, ..., Qᵥ}, definieras Mᵥ 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 {Q₁, Q₂, ..., Qᵥ} är likvärdiga (d.v.s. varje Pᵢ 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 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, är Mᵢ 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 för Mᵢ har färre element än params-matrisen för Mₑ, är Mᵢ bättre än Mₑ.
  • Om Mᵥ annars har mer specifika parametertyper än Mₓär Mᵥ bättre än Mₓ. Låt {R1, R2, ..., Rn} och {S1, S2, ..., Sn} representera de oinstifierade och oexpandererade parametertyperna för Mᵥ och Mₓ. Mᵥparametertyper är mer specifika än Mₓom Rx för varje parameter inte är mindre specifik än Sxoch för minst en parameter är Rx mer specifik än Sx:
    • En typparameter är mindre specifik än en icke-typparameter.
    • 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 är en icke-lyftad operatör medan den andra är en lyftad operatör, är den icke-lyftade det bättre valet.
  • Om ingen funktionsmedlem visade sig vara bättre och alla parametrar i Mᵥ har ett motsvarande argument medan standardargument måste ersättas med minst en valfri parameter i Mₓär Mᵥ bättre än Mₓ.
  • Om Mᵥ för minst en parameter använder bättre parameteröverföringsalternativ (§12.6.4.4) än motsvarande parameter i Mₓ och ingen av parametrarna i Mₓ använda det bättre parameteröverföringsalternativet än Mᵥär 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, resulterar samtalen M1(i) och M1(i + 5) i att båda överbelastningarna är tillämpliga. I sådana fall är metoden med parameteröverföringsläget "värde" det bättre valet av 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

Med 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 T₂är C₁ en bättre konvertering än C₂ om något av följande gäller:

  • E matchar exakt T₁ och E matchar inte exakt T₂ (§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 enda bästa metoden från metodgruppen för konvertering C₁, och T₂ inte är kompatibel med den enda bästa metoden från metodgruppen för konvertering C₂

12.6.4.6 Exakt matchande uttryck

Med ett uttryck E och en typ Tmatchar EexaktT 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 är T 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 E i samband med parameterlistan över D (§12.6.3.12), och det finns en identitetskonvertering från X till returtypen för D
    • E är en async lambda utan returvärde och D har en returtyp som är en icke-generisk «TaskType»
    • Antingen är E 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:
      • Kroppen i E är ett uttryck som matchar exakt Y
      • Kroppen i E är ett block där varje retursats returnerar ett uttryck som exakt matchar Y

12.6.4.7 Bättre konverteringsmål

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

  • Det finns en implicit konvertering från T₁ till T₂ och det finns ingen implicit konvertering från T₂ till T₁
  • 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 integrerad typ och T₂ är S₂ eller S₂? där S₂ är en osignerad integrerad typ. Specifikt:
    • S₁ är sbyte och S₂ är byte, ushort, uinteller ulong
    • S₁ är short och S₂ är ushort, uinteller ulong
    • S₁ är int och S₂ är uinteller ulong
    • S₁ är long och S₂ är ulong

12.6.4.8 Överbelastning i generiska klasser

Anmärkning: Även om signaturer som deklareras 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 (innan ersättning av typargument), om det finns och rapporterar annars ett fel. slutkommentar

Exempel: Följande exempel visar ö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 funktionsmedlem med samma parameterlista som delegate_type av 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 kompileringstidkontroll på varje medlem i den kända uppsättningen av funktionsmedlemmar, för att avgöra om någon av dem med säkerhet aldrig skulle kunna anropas vid körning. För varje funktionsmedlem skapas F en modifierad parameter och argumentlista:

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

För att F ska klara kontrollen skall 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 den ändrade parameterlistan uppfyller sina begränsningar (§8.4.5).
  • Om typparametrarna för F ersattes i steget ovan uppfylls deras begränsningar.
  • Om F är en statisk metod ska metodgruppen inte ha orsakats av en member_access vars mottagare vid kompileringstiden är en variabel eller ett värde.
  • Om F är en instansmetod ska metodgruppen inte ha uppstått från en member_access vars mottagare vid kompileringstid är känd för att vara en typ.

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

12.6.6 Funktionsmedlemsanrop

12.6.6.1 Allmänt

Detta underavsnitt beskriver den process som sker vid körtid 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, instansegenskapsaccessorer och indexeraccessorer. 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 funktionsmedlem och, om M är 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 klassificeras E som en variabel.
    • Om E inte klassificeras som en variabel, eller om V inte är en skrivskyddad strukturtyp (§16.2.2), och E är en av följande:
      • en indataparameter (§15.6.2.3.2) eller
      • ett readonly fält (§15.5.3), eller
      • en readonly referensvariabel eller avkastning (§9.7),

    sedan skapas en tillfällig lokal variabel av typen Eoch värdet för E 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 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_typeutfö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 class_typeSystem.Enum;, annars är det System.ValueType.
    • Värdet för E kontrolleras för att vara giltigt. Om värdet för E är null genereras en System.NullReferenceException och inga ytterligare steg körs.
    • Den funktionsmedlemsimplementering som ska anropas bestäms:
      • Om bindningstidstypen för E är ett gränssnitt, är funktionsmedlemmen som ska anropas implementeringen av M som tillhandahålls av instansens körningstidstyp 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 som tillhandahålls av körningstiden för den instans som refereras av E.
      • Annars om M är en virtuell funktion, ska funktionsmedlemmen som ska anropas vara implementeringen av M som tillhandahålls av körningstidenstypen för instansen som refereras av E. Denna funktionsmedlem bestäms genom tillämpning av reglerna för att fastställa den mest härledda implementeringen (§15.6.4) av M med avseende på körtidstypen för den instans som refereras av E.
      • Annars är M 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 är alltid 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 boxade instansen. slutkommentar

12.7 Dekonstruktion

Dekonstruktion är en process där ett uttryck omvandlas till en tuppel 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 Edekonstrueras till ett tuppeluttryck med n element på följande sätt:

  • Om E är ett tupeluttryck med n element, är resultatet av dekonstruktion uttrycket E.
  • Annars, om E har tuppeltypen (T1, ..., Tn) med n element, då utvärderas E till en temporär variabel __voch resultatet av dekonstruktion är uttrycket (__v.Item1, ..., __v.Itemn).
  • Om uttrycket E.Deconstruct(out var __v1, ..., out var __vn) vid kompileringstidpunkten resulterar i en unik instans eller extension method, utvärderas det uttrycket och resultatet av dekonstruktionen är uttrycket (__v1, ..., __vn). En sådan metod kallas för en dekonstruktor.
  • Annars kan E inte dekonstrueras.

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

Obs: Ett uttryck av typen 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
    ;

Note: 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_expression, null_forgiving_expression, pointer_member_access och 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, till exempel

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; regular (interpolated_regular_string_expression) och verbatim (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 på följande sätt:

Regel kontextkrav
Interpolerad_Regular_String_Mid Identifieras endast efter en Interpolated_Regular_String_Start, mellan följande interpoleringar 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_Mideller token som kan vara en del av regular_interpolation, inklusive token för eventuella interpolated_regular_string_expressionsom ingår i sådana interpoleringar.
Interpolated_Verbatim_String_MidVerbatim_Interpolation_FormatInterpolated_Verbatim_String_End Erkännandet av dessa tre regler följer motsvarande regler ovan, där varje nämnd regelbunden grammatikregel ersätts med motsvarande ordagrann regel.

Note: 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 implementeringsinformation 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 typ string.

Anmärkning: Skillnaderna mellan möjliga typer av 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 en 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 skapa den slutliga string som ska interpoleras i 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 av detta konstantuttryck och justeringen vara tecknet (positiv eller negativ) på värdet av detta konstantuttryck:

  • 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 uttryckets typ ä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; I annat fall 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ängliteralen 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ängsliteralen består i ordning av:

  • Tecknen i Interpolated_Regular_String_Start eller Interpolated_Verbatim_String_Start
  • Tecknen, om några, i Interpolated_Regular_String_Mid eller Interpolated_Verbatim_String_Mid
  • Då om N ≥ 1 för varje tal I från 0 till N-1
    • En platshållarspecifikation:
      • Ett vänster klammerparentes ({) tecken
      • Decimalrepresentationen av I
      • Om motsvarande regular_interpolation eller verbatim_interpolation sedan har ett 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, om några, av den motsvarande regular_interpolation eller verbatim_interpolation
      • Ett högerparentestecken (})
    • Tecknen i Interpolated_Regular_String_Mid eller Interpolated_Verbatim_String_Mid som omedelbart följer efter den motsvarande interpolationen, om någon
  • Slutligen tecknen i Interpolated_Regular_String_End eller Interpolated_Verbatim_String_End.

De efterföljande argumenten är uttrycket :s från interpolationerna, om några, i följd.

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

Exempel:

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

  • X-formatsspecifikationen som formaterar heltal som hexadecimala tal med versaler,
  • 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 }.

Givet:

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

Då:

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 av formen I eller av formen I<A₁, ..., Aₑ>, där I är en enkel identifierare och I<A₁, ..., Aₑ> är en valfri typargumentlista. När inga type_argument_list har angetts bör du överväga att e vara noll. simple_name utvärderas och klassificeras på följande sätt:

  • 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 namn 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 attribut 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), börjande med instanstypen för den omedelbart omslutande typdeklarationen och fortsättande med instanstypen för varje omslutande klass- eller structdeklaration (om sådan finns):
    • Om e är noll och deklarationen av T innehåller en typparameter med namnet Irefererar simple_name till den typparametern.
    • Annars, om en medlemssökning (§12.5) av I i T med argument av e typ genererar 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 strukturtpen, om sökningen identifierar en instansmedlem, och om referensen sker inom block av en instanskonstruktor, en instansmetod eller en instansaccessor (§12.2.1), är resultatet detsamma som en medlemsåtkomst (§12.8.7) i formen this.I. Detta kan bara inträffa när e är noll.
      • Annars är resultatet detsamma som en medlemsåtkomst (§12.8.7) i form av T.I eller T.I<A₁, ..., Aₑ>.
  • I annat fall utvärderas följande steg tills en entitet finns för varje namnområde N, från och 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 typ, är simple_name tvetydigt 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 tvetydigt 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 förekommer är omsluten 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 typ som skapats med de angivna typargumenten.
      • Annars, om namnrymderna som importeras av using_namespace_directivei namnområdesdeklarationen innehåller fler än en typ med namnet I och e typparametrar, så är simple_name tvetydig 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 enkelt_namn ett enkelt ignoreringsobjekt, 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 inom parenteser.

parenthesized_expression
    : '(' expression ')'
    ;

En parenthesized_expression utvärderas genom att utvärdera -uttrycket inom parenteserna. Om det 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 Tuppeluttryck

En tuple_expression representerar en tuppel 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 tuppel som innehåller implicita 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 ett tubel.

En deconstruction_expressionvar (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ärför ett deklarationsuttryck (§12.17). Därför kan en deconstruction_expression bara ske på vänster sida av ett enkelt tilldelningsuttryck.

Ett tuppeluttryck har en typ om och endast om vart och ett av dess elementuttryck Ei har en typ Ti. Typen ska vara en tupletyp med samma kardinalitet som tupleuttrycket, där varje element anges av följande:

  • Om tuppeln i motsvarande position har ett namn Niska tuppelns typelement vara Ti Ni.
  • Om Ei är i formen Ni eller E.Ni eller E?.Ni ska tupelelementet vara Ti Ni, såvida inget av följande gäller:
    • Ett annat element i tuppelns uttryck har namnet Ni, eller
    • Ett annat tupleelement utan namn har ett tupleelementuttryck i formen av Ni eller E.Ni eller E?.Nieller
    • Ni är av formen ItemX, där X är en sekvens av icke-0-initierade decimalsiffror som kan representera positionen för ett tupelelement, och X inte representerar elementets position.
  • I annat fall ska tupelelementets typ-element vara Ti.

Ett tuputtryck utvärderas genom att utvärdera varje elementuttryck i ordning från vänster till höger.

Ett tuppelvärde kan erhållas från ett tuppeluttryck genom att konvertera det till en tuppeltyp (§10.2.13), genom att omklassificera det som ett värde (§12.2.2) eller genom att göra det till målet för en dekonstruerande tilldelning (§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 tuppeluttrycken giltiga. De första två, t1 och t2, använder inte typen av tuppelns uttryck, utan tillämpar i stället en implicit tuppelkonvertering. När det gäller t2förlitar sig den implicita tupleomvandlingen på de implicita konverteringarna 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 tupler ibland kan leda till flera lager av parenteser, särskilt när tuplarnas uttryck är det enda argumentet till metodanropet.

slutexempel

12.8.7 Medlemsåtkomst

12.8.7.1 Allmänt

En member_access består av en primary_expression, en predefined_typeeller en qualified_alias_memberfö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 formen E.I eller i formen E.I<A₁, ..., Aₑ>, där E är en primary_expression, predefined_type eller qualified_alias_member,I är ett enda identifierarnamn och <A₁, ..., Aₑ> är en valfri type_argument_list. När inga type_argument_list har angetts bör du överväga att e 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 den här körningsklassificeringen leder till en metodgrupp ska medlemsåtkomsten vara primäruttryck för en anropsexpression.

member_access utvärderas och klassificeras enligt följande:

  • 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, blir resultatet den typen som skapas med de angivna typargumenten.
  • Om E klassificeras som en typ, om E inte är en typparameter, och om en medlemssökning (§12.5) av I i E med K typparametrar genererar en matchning, utvärderas E.I och klassificeras enligt följande:

    Obs: När resultatet av en sådan medlemssökning är en metodgrupp och K är noll kan metodgruppen innehålla metoder med 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 en viss 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 E.I exakt som om I 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 konstanten.
    • Om I identifierar en uppräkningsmedlem är resultatet ett värde, nämligen värdet för den uppräkningsmedlemmen.
    • Annars är E.I en ogiltig medlemsreferens och ett kompileringsfel inträffar.
  • Om E är en egenskapsåtkomst, indexerareåtkomst, variabel eller värde, vars typ är Toch en medlemssökning (§12.5) av I i T med argument av K typ skapar en matchning, utvärderas E.I och klassificeras enligt följande:
    • Först erhålls värdet för egenskaps- eller indexeråtkomst om E är en egenskap eller index åtkomst (§12.2.2), och E omklassificeras som ett värde.
    • Om I identifierar en eller flera metoder blir 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 baserat på den första deklarationen eller åsidosättningen av egenskapen som hittas när man börjar 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 för E ä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, är 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 struct-instansen som anges av E.
    • Om I identifierar en instanshä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), och referensen inte inträffar som vänster sida av a += eller -= operator, bearbetas E.I exakt som om I var ett instansfält.
      • Annars är resultatet en händelseåtkomst med ett associerat instansuttryck av E.
  • I annat fall görs ett försök att bearbeta E.I som ett tilläggsmetodanrop (§12.8.10.3). Om detta misslyckas är E.I en ogiltig medlemsreferens och ett bindningstidsfel inträffar.

12.8.7.2 Identiska enkla namn och typnamn

I ett medlemsåtkomstformulär E.I, om E är en enda 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), ), därefter tillåts båda möjliga betydelser av E. Medlemssökningen av E.I är aldrig tvetydig, eftersom I nödvändigtvis ska vara medlem av typen E i båda fallen. Med andra ord tillåter regeln bara åtkomst till statiska medlemmar och kapslade typer av E där 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
    }
}

Endast för exponeringsändamål, inom A-klassen, avgränsas förekomsterna av den Color-identifierare som refererar till Color-typ med «...», och de som refererar till fältet Color avgränsas inte.

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 null-villkorsuttryck vilket resulterar i att resultattypen kan vara void, se (§12.8.11).

En null_conditional_member_access består av en primary_expression följd av de två tokenen ”?” och ”.”, följt av en identifierare med en valfri type_argument_list, följd av noll eller fler dependent_accesssom kan föregås 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 i formen av P?.A. Innebörden av E bestäms på följande sätt:

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

    Låt T vara 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 värdetyp som inte kan nollföras är typen av ET?, och innebörden av E är densamma som innebörden av:

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

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

    • Annars är typen av EToch innebörden av E är densamma som innebörden av:

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

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

  • Annars:

    Låt T vara typen av uttryck 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 värdetyp som inte kan nollföras är typen av ET?, och innebörden av E är densamma som innebörden av:

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

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

    • Annars är typen av EToch innebörden av E är densamma som innebörden av:

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

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

Obs: I ett uttryck av formen:

P?.A₀?.A₁

om P utvärderas till null utvärderas varken A₀ eller A₁. 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 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 säkerhetskontext (§16.4.12) är värdet, typen, klassificeringen och säkerhetskontexten för dess primäruttryck.

null_forgiving_expression
    : primary_expression null_forgiving_operator
    ;

null_forgiving_operator
    : '!'
    ;

Note: Null-forgiving postfixoperatorn och den logiska negationens prefixoperator (§12.9.4), medan det representeras av samma lexikala tecken (!), är distinkta. Endast de 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 det här underavsnittet och de följande syskonunderavsnitten är villkorat 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 null; och för att åsidosätta en kompilator som utfärdar en varning om nullabilitet.

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 du tillämpar operatorn null-forgiving på ett sådant uttryck informeras kompilatorns statiska null-tillståndsanalys om att null-tillståndet är i inte null; som 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 avrefereras för att få åtkomst till egenskapen Name, och varningen "avrefererar ett eventuellt nullvärde" kan ignoreras med hjälp av !.

slutexempel

Exempel: Operatorn null-forgiving bör användas med försiktighet:

#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 tar bort alla varningar på x. Men om x är null vid körning, kommer ett undantag att genereras eftersom null inte kan omvandlas till int.

slutexempel

12.8.9.3 Åsidosätta andra null-analysvarningar

Förutom att åsidosätta bestämningar som kanske null som ovan, kan det finnas andra omständigheter där det är önskvärt att åsidosätta en kompilators statiska analys av null-tillstånd som påpekar att ett uttryck kräver en eller flera varningar. Tillämpning av null-forgiving-operatorn på ett sådant uttryck innebär en begäran till kompilatorn att inte utfärda 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 metod Assignparametrar, lv & rv, är string?, där lv är en utdataparameter och utför en enkel tilldelning.

Metod M skickar variabeln s, av typen string, som Assign:s utdataparameter. Kompilatorn ger en varning eftersom s inte är en nullbar variabel. Eftersom det andra argumentet i Assigninte kan vara null används null-förlåtelseoperatorn för att upphäva 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? ')'
    ;

Ett primary_expression kan vara en null_forgiving_expression om och endast om det har en delegerad_typ.

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

  • primary_expression har kompileringstidstyp dynamic.
  • Minst ett argument för den valfria argument_list har kompileringstidstyp 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 av en delegattyp, är invocation_expression ett delegatanrop (§12.8.10.4). Om primary_expression varken är en metodgrupp eller ett värde för en delegate_typeuppstå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 en delegering för returns-no-value, är resultatet inget. 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.
  • Annars, om invocation_expression anropar en return-by-ref-metod (§15.6.1) eller ett return-by-ref-ombud, ä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 en klasstyp T, väljs den associerade typen från den första deklarationen eller åsidosättningen av metoden som hittades när du 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 return-by-value-delegat, och resultatet är ett värde med en typ associerad med returtypen för metoden eller delegaten. Om anropet är av en instansmetod och mottagaren är av en klasstyp T, väljs den associerade typen från den första deklarationen eller åsidosättningen av metoden som hittades när du började med T och söker igenom dess basklasser.

12.8.10.2 Metodanrop

För ett metodanrop ska primäruttryck av anropsuttryck 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 associerad med metodgruppen M:
    • Om F inte är generisk är F 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 är F en kandidat när:
      • Typbestämning (§12.6.3) lyckas, bestämmer 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 över F deras begränsningar (§8.4.5), och parameterlistan över F gäller för A (§12.6.4.2)
    • Om F är generisk och M innehåller en typargumentlista är F 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 över F sina begränsningar (§8.4.5), och parameterlistan över F gäller för 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 deklarerats i en bastyp av C bort 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 en medlemssökning på en typparameter med 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 överbelastningslösning i §12.6.4. Om det inte går att identifiera en enda bästa metod är metodanropet tvetydigt och ett bindningstidsfel inträffar. Vid utförandet av överlagringsresolution beaktas parametrarna hos en generisk metod efter att typargumenten (antingen angivna eller härledda) har ersatts för motsvarande metodtypparametrar.

När en metod har valts och verifierats under bindningstiden enligt ovanstående steg bearbetas själva körningsanropet enligt reglerna för funktionsmedlemsanrop som beskrivs i §12.6.6.

Note: Den intuitiva effekten av de lösningsregler som beskrivs ovan är följande: Om du vill hitta den metod som anropas av en 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, icke-åsidosättningsmetoddeklaration 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 en av formerna

«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 anropet som ett 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 giltig om:

  • Cᵢ är en icke-generisk och icke-nästlad klass.
  • Namnet på Mₑ är identifierare
  • Mₑ är tillgängligt och tillämpligt när det tillämpas på argumenten som en statisk metod enligt ovan
  • Det finns en implicit identitet, referens eller boxningskonvertering från uttryck till typen av den första parametern i 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 namnområden 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 överlagringslö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 C som mål bearbetas metodanropet sedan som en statisk metodanrop (§12.6.6).

Note: Till skillnad från instansmetodanropet utlöses inget undantag när uttryck utvärderas till en null-referens. I stället skickas det här null-värdet till tilläggsmetoden precis som via ett vanligt statiskt 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 har B-metoden företräde framför den första tilläggsmetoden och Cmetoden 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 över C.Goch E.F har företräde framför både D.F och C.F.

slutexempel

12.8.10.4 Anrop av delegater

För ett delegeringsanrop ska primäruttryck för anropsuttryck vara ett värde av typen delegattyp. Med tanke på att delegate_type vara funktionsmedlem med samma parameterlista som delegate_typeska delegate_type vara tillämpligt (§12.6.4.2) med avseende på argument_list i invocation_expression.

Körningsbearbetningen av ett delegeringsanrop av typen D(A), där D är ett primäruttryck av en delegeringstyp och A är en valfri argumentlista, 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 för D kontrolleras för att vara giltigt. Om värdet för D är nullgenereras en System.NullReferenceException och inga ytterligare steg körs.
  • Annars är D 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_accesskan 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 får endast inkluderas om null_conditional_member_access eller null_conditional_element_access har en delegate_type.

Ett null_conditional_invocation_expression uttryck E har formen P?A; där A är resten av den syntaktiskt likvärdiga null_conditional_member_access eller null_conditional_element_access, kommer A därför att börja med . eller [. Låt PA beteckna sammanfogningen av P och A.

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

if ((object)P != null) PA

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

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

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

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

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

  • I annat fall är innebörden av E samma som innebörden av block:

    { return E; }
    

    och i sin tur beror innebörden av detta block på om E är syntaktiskt likvärdig med ett 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_listföljt av en "]" token. argument_list består av ett eller flera arguments, 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 kompileringstidstyp dynamic.
  • Minst ett uttryck i argument_list har kompileringstidstyp dynamic och primary_no_array_creation_expression har ingen matristyp.

I det här fallet klassificerar kompilatorn element_access som ett värde av typen dynamic. Reglerna nedan för att fastställa betydelsen av element_access tillämpas sedan under körningen, med körningstiden istället för kompileringstiden för de primary_no_array_creation_expression och argument_list uttryck som har kompileringstypen dynamic. Om primary_no_array_creation_expression inte har kompileringstidstypen dynamicgenomgår åtkomsten av elementet en begränsad kompileringstidskontroll enligt beskrivningen i §12.6.5.

Om primary_no_array_creation_expression av en element_access är ett värde av en array_type, så ä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 indexermedlemmar, 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, eller 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 för 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 till long är 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 för P kontrolleras för att vara giltigt. Om värdet för P ä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 av matrisinstansen som refereras av P. Om ett eller flera värden ligger utanför intervallet, utlöses ett System.IndexOutOfRangeException-undantag och inga fler 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 för element_access vara en variabel eller ett värde för en klass, struct eller gränssnittstyp, och den här typen 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 åsidosätter deklarationer 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 gäller för A (§12.6.4.2), tas I bort från uppsättningen.
    • Om I gäller för A (§12.6.4.2), tas alla indexerare som deklarerats i en bastyp av S bort från uppsättningen.
    • Om I gäller för A (§12.6.4.2) och S är en annan klasstyp än objecttas 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 överbelastningsupplösning 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 indexeråtkomsten är ett uttryck som klassificeras som en indexerå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 en indexerare används orsakar det ett anrop av antingen get-tillträdaren eller set-tillträdaren för indexeraren. 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 erhålla 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 tokenen “?” och “[”, följt av en argument_list, följt av en “]” token, följt av noll eller flera dependent_accesssom 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 null-villkorsuttryck vilket resulterar i att resultattypen kan vara void, se (§12.8.11).

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

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

    Låt T vara typen av uttryck 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 värdetyp som inte kan nollföras är typen av ET?, och innebörden av E är densamma som innebörden av:

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

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

    • Annars är typen av EToch innebörden av E är densamma som innebörden av:

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

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

  • Annars:

    Låt T vara typen av uttryck 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 värdetyp som inte kan nollföras är typen av ET?, och innebörden av E är densamma som innebörden av:

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

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

    • Annars är typen av EToch innebörden av E är densamma som innebörden av:

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

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

Obs: I ett uttryck av formen:

P?[A₀]?[A₁]

om P utvärderas till null utvärderas varken A₀ eller A₁. 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 av en instanskonstruktor, en instansmetod, en instansåtkomst (§12.2.1) eller en finaliserare. 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. Värdets typ ä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 i 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 inom vilken 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 inom vilken användningen sker och variabeln representerar den struct som skapas.
    • Om konstruktordeklarationen inte har någon konstruktorinitierare fungerar variabeln this exakt på samma sätt som en utdataparameter av typen struct. Detta innebär särskilt att variabeln definitivt ska tilldelas i varje exekveringsväg för instanskonstruktorn.
    • I annat fall 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 i 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) representerar variabeln this den struct som metoden eller accessorn anropades för.
      • Om structen är en readonly structfungerar variabeln this exakt samma som en indataparameter av structtypen
      • Annars beter sig variabeln this exakt på samma sätt som en ref parameter av struct-typen
    • Om metoden eller accessorn är en iterator eller asynkron funktion representerar variabeln this en kopia av den struktur för vilken metoden eller accessorn anropades och fungerar exakt på samma sätt som en värdeparameter av strukturtypen.

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

12.8.15 Grundläggande åtkomst

En base_access består av nyckelordet base följt av antingen en ”.” token, en identifierare och en 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 en instanskonstruktors brödtext, en instansmetod, en instansaccessor (§12.2.1), eller en finalisator. När base.I inträffar i en klass eller struct ska I ange en medlem i basklassen för den klassen eller structen. På samma sätt ska en tillämplig indexerare finnas i basklassen när base[E] inträffar i en klass.

Vid bindningstid utvärderas base_access uttryck av formen 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 förekommer. Därför motsvarar base.I och base[E]this.I och this[E], förutom att this betraktas som en instans av basklassen.

När en base_access refererar till en virtuell funktionsmedlem (en metod, egenskap eller indexerare) ändras fastställandet 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) av funktionsmedlemmen med avseende på B (i stället för med avseende på körningstyp this, vilket brukar vara fallet vid en icke-basåtkomst). Inom en överskrivning 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.

Note: Till skillnad från thisär base 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 dekrementåtgärd ska vara ett uttryck som klassificeras som en variabel, en egenskapsåtkomst eller en indexerå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 kompileringstyp dynamic och följande regler tillämpas vid körning med hjälp av körningstypen för primary_expression.

Om operanden för en postfix inkrement- eller dekrementoperation är en egenskaps- eller indexerartillgång, ska egenskapen eller indexeraren ha både en get- och en set-accessor. Om så inte är fallet uppstår ett bindningstidsfel.

Unary operator overload resolution (§12.4.4) används för att välja en specifik operatorimplementering. Fördefinierade operatorer för ++ och -- finns för följande typer: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimaloch alla uppräkningstyper. De fördefinierade ++ operatorerna returnerar värdet som genereras genom att lägga till 1 till operanden, och de fördefinierade -- operatorerna returnerar värdet som genereras 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 heltalstyp eller enum-typ, kastas en System.OverflowException.

Det ska finnas 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 minskningsåtgärd för 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 för x sparas.
    • Det sparade värdet för x 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 för x blir resultatet av åtgärden.
  • Om x klassificeras som åtkomst till en egenskap eller indexerare:
    • Instansuttrycket (om x inte är static) och argumentlistan (om x är en indexeringsåtkomst) som är associerad med x utvärderas, och resultaten används i efterföljande hämtnings- och inställningsanrop av accessor.
    • Get-accessorn för x anropas och det returnerade värdet sparas.
    • Det sparade värdet för x 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 åtkomstorn för x anropas med det här värdet som värdeargument.
    • Det sparade värdet för x blir resultatet av åtgärden.

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

En operator ++ eller operator -- implementering 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 delegeringar används för att hämta instanser av delegattyper.

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.

Notering: Delegeringsuttryck 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 detta 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
    ;

Den typen av en object_creation_expression ska vara en class_type, en value_typeeller en type_parameter. Den 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 typ ä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 argumentlistan har kompileringstypen dynamic så binds objekt_skapat_uttryck dynamiskt (§12.3.3) och följande regler gäller vid körtid med körtidstypen för argumenten i argumentlistan som har kompileringstypen dynamic. Objektskapandet genomgår dock en begränsad kompileringstidskontroll enligt beskrivningen i §12.6.5.

Bindningstidsbehandlingen av en object_creation_expression av formuläret new 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_typeuppstå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 new 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 klass 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 samlingsinitierare. Det är inte möjligt för uttryck i objektinitieraren att referera till det nyligen skapade objektet som initieras.

En medlemsinitierare som specificerar ett uttryck efter 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 de krav som anges 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 skapandet och initieringen 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 Rectanglekonstruktor allokerar de två inbäddade Point-instanserna kan de användas för att initiera inbäddade Point instanser 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 inbäddade Point instanser 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 Samlinginitierare

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 samlinginitierare består av en sekvens av elementinitierare som omges av { och } tokenar och avgränsas 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 elementinitialisering med ett enda uttryck kan skrivas utan klammerparenteser, men får då inte vara ett tilldelningsuttryck, för att undvika tvetydighet med medlemsinitialisatorer. Den icke-tilldelningsuttryck 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 överbelastningsupplösning 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 för varje elementinitierare.

Exempel: Följande visar en klass som representerar en kontakt med ett namn och en lista med telefonnummer samt skapande och initiering 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 matrisen new int[10,20] genererar en matrisinstans av typen int[,], och matrisens skapandeuttryck ny int[10][,] skapar en matrisinstans av typen int[][,]. slutexempel

Varje uttryck i uttryckslistan ska vara av typen int, uint, long, eller ulong, eller implicit konvertibel 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 matrisernas layout 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 bästa gemensamma typen (§12.6.3.15) av uppsättningen uttryck i matrisinitieraren. För en flerdimensionell array, 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.

Arrayinitierare 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 standardvärdena (§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: Påståendet

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

skapar en endimensionell matris med 100 element av typen int[]. Det ursprungliga värdet för varje element är null. Det är inte möjligt för samma matriscreeringsuttryck att också instansiera undermatriserna, och påståendet

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, vilket innebär att undermatriserna 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 implicit kan konverteras till det andra, så det finns ingen vanlig typ. Ett explicit matrisskapande uttryck måste användas i det här fallet, till exempel genom att ange typen 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 skapa en instans av en delegate_typ.

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

Argumentet för ett uttryck för att skapa delegat 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 metodkroppen för delegatmålet. Om argumentet är ett värde identifierar det en delegerad instans som du kan skapa en kopia av.

Om -uttrycket har kompileringstidstypen dynamic, är delegate_creation_expression dynamiskt bunden (§12.8.17.6), och reglerna nedan tillämpas vid körning med körningstypen för -uttrycket. 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 delegeringsskapande uttrycket på samma sätt som en omvandling av metodgrupp (§10.8) från E till D.

  • Om E är en anonym funktion bearbetas uttrycket för att skapa en delegate på samma sätt som en konvertering av anonym funktion (§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 en nyligen skapad delegering med en anropslista med en enda post som anropar E.

Körningsbearbetningen av ett delegatskapandeuttryck av formen ny D(E), där D är en delegattyp och E är ett uttryck, består av följande steg:

  • Om E är en metodgrupp utvärderas delegeringsuttrycket som en metodgruppskonvertering (§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 för E är nullgenereras en System.NullReferenceException och inga ytterligare steg körs.
    • En ny instans av delegattypen 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 som har ett enda inlägg 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, resulterar ett nytt ombud. inget befintligt ombud har ändrat sitt innehåll. slutkommentar

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

Exempel: Som beskrivs 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 en delegerare som refererar till den andra Square-metoden, eftersom metoden matchar exakt parameterlistan och returtypen 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. Medlemmarna i en anonym typ är en sekvens av skrivskyddade egenskaper som härleds från en anonym objektinitiator 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 dess Equals-metoden genereras automatiskt av kompilatorn och kan inte refereras till 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 och GetHashcode av egenskaperna, 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 nullvillkorlig projektioninitialisator §12.8.8 eller en basåtkomst (§12.8.15). Detta kallas för en projektionsinitierare 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 av denna form ä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 typeof(T) == typeof(T) alltid sant. Typen kan inte vara dynamic.

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

Anmärkning: En unbound_type_name liknar en type_name (§7.8) förutom att en unbound_type_name innehåller generic_dimension_specifiers där en type_name innehåller type_argument_lists. slutkommentar

När operanden för en typeof_expression är en sekvens av tokens som uppfyller grammatiken för både unbound_type_name och type_name, nämligen när den inte innehåller en generic_dimension_specifier eller type_argument_list, anses sekvensen av tokens 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 motsvarar den oavgränsade 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 det System.Type objektet 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 vilken typ som helst.

Note: Det här speciella System.Type-objektet är användbart i klassbibliotek som tillåter reflektion över metoder i 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 körtidstypen som var bunden till typparametern. Om körningstypen är en nullbar referenstyp blir resultatet den motsvarande icke-nullbara referenstypen. Den typeof-operatorn kan också användas på en konstruerad typ eller en obunden generisk typ (§8.4.4). Det System.Type objektet för en obundet allmän typ är inte detsamma som det System.Type objektet av instanstypen (§15.3.2). Instanstypen är alltid en slutet konstruktionstyp under körning, så System.Type-objektet beror på vilka körningstidsargument 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 operand till storlek skall vara en unmanaged_type (§8.8).

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

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

uttryck 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 motsvarar storleken på dess underliggande typ, som nämnts ovan. För alla andra operandtyper anges sizeof operatorn i §23.6.9.

12.8.20 De kontrollerade och okontrollerade operatorerna

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

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

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

Operatorn checked utvärderar det inneslutna uttrycket i en kontrollerad kontext och unchecked-operatorn 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 det angivna överflödeskontrollkontexten.

Överflödskontrollkontexten kan också styras via checked- och unchecked-instruktioner (§13.12).

Följande operationer påverkas av den överflödeskontrollkontext som har upprättats av de markerade och okontrollerade operatorerna och uttrycken:

  • De fördefinierade ++- 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 +, -, *och / binära operatorerna (§12.10), när båda operanderna är av heltals- 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:

  • I en checked kontext uppstår ett kompileringsfel om åtgärden är ett konstant uttryck (§12.23). Annars genereras en System.OverflowException när åtgärden utförs vid körning.
  • I en unchecked kontext trunkeras resultatet genom att alla bitar i hög ordning 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ågon checked- eller unchecked-operatör eller instruktion, är standardkontexten för kontroll av överflöde ej kontrollerad, såvida inte externa faktorer (till exempel kompilatorväxlar och konfiguration av exekveringsmiljön) kräver kontrollerad utvärdering.

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

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 genererar metoden F en System.OverflowException, och metoden G returnerar –727379968 (de lägre 32 bitarna i resultatet som inte ligger inom intervallet). Beteendet för metoden 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 översvämningar som uppstår vid utvärdering av konstantuttrycken i F och H orsakar kompileringsfel som rapporteras eftersom uttrycken utvärderas i en checked-kontext. Ett överflöde uppstår också när det konstanta uttrycket utvärderas i G, men eftersom utvärderingen sker i ett unchecked-sammanhang rapporteras inte överflödet.

slutexempel

Operatorerna checked och unchecked påverkar bara överflödeskontrollkontexten för de åtgärder som finns i token "(" 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 standardkontexten för kontroll av spill.

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: Operatorer och instruktioner för checked och unchecked 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 multiplikation av två flyttal aldrig i ett undantag vid överflöd, även inom en explicit kontrollstruktur. 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'
    ;

En 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_defaulteller måltypen för konverteringen för en default_value_expression.

En 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: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool,; eller
  • vilken enumereringstyp som helst.

12.8.22 Stack-allokering

Ett uttryck för stackallokering allokerar ett minnesblock från call stacken. Den 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.

Reglerna för säker kontext 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
    ;

unmanaged_type (§8.8) anger vilken typ av objekt som ska lagras på den nyligen allokerade platsen och uttryck anger antalet objekt. Tillsammans anger dessa den nödvändiga allokeringsstorleken. Typen av uttryck 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.

Under körning, 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 en 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 av stackalloc_element_initializers.
  • Om konstantuttryck utelämnas, härleds det till antalet stackalloc-elementinitialiseringar.
  • Om konstantuttryck finns ska detta uttryck vara lika med antalet stackalloc_element_initializer:er.

Varje stackalloc_element_initializer ska ha en implicit konvertering till unmanaged_type (§10.2). stackalloc_element_initializerinitierar 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.

Om en stackalloc_expression inträffar direkt som initieringsuttrycket för en local_variable_declaration (§13.6.2), där local_variable_type antingen är en pekartyp (§23.3) eller härledd (var), är resultatet av stackalloc_expression en pekare av typen T* (§23.9). I det här fallet måste stackalloc_expression visas i osäker kod. Annars är resultatet av en stackalloc_expression en instans av typen Span<T>, där T är unmanaged_type:

  • Span<T> (§C.3) är en ref-strukturtyp (§16.2.3), som presenterar ett minnesblock, här blocket som allokerats av stackalloc_expression, som en indexerbar samling av typat element (T).
  • Resultatets Length-egenskap returnerar antalet allokerade objekt.
  • Resultatets indexerare (§15,9) returnerar en variable_reference (§9,5) till en post i det allokerade blocket och är intervallkontrollerad.

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

Obs: Det finns inget sätt att uttryckligen frigöra minne som allokerats med hjälp av stackalloc. slutkommentar

Alla stackallokerade minnesblock som skapats under exekveringen av en funktionsmedlem frigörs automatiskt när den funktionsmedlemmen returnerar.

Förutom operatorn stackalloc tillhandahå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 span8resulterar stackalloc i en Span<int>, som konverteras av en implicit operator till ReadOnlySpan<int>. På samma sätt konverteras den resulterande span9 för Span<double>till 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 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 behandlas uttrycket av kompatibilitetsskäl 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 om 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 om en named_entity_target har typen dynamic.

En nameof_expression är ett konstant uttryck med typen stringoch har ingen effekt vid körning. Mer specifikt så utvärderas inte dess named_entity och ignoreras vid definitiv 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 formaterings_tecken 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åde:

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 på nameof(System.Collections.Generic) 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

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

12.9 Unary operator

12.9.1 Allmänt

+, -, ! (logisk negation endast §12.9.4), ~, ++, --, cast och await-operatorerna kallas de unära operatorerna.

Obs: Den postfixa null-förlåtande operatorn (§12.8.9), !, på grund av dess natur som endast påverkar kompileringstiden och inte kan överbelastas, 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 av en unary_expression har kompileringstidstypen dynamic, är den dynamiskt bunden (§12.3.3). I det här fallet är kompileringstidstypen för unary_expressiondynamic, 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 operation av formen +x, tillämpas operatöröverbelastningslösning (§12.4.4) för att välja en specifik operatörsimplementering. Operand konverteras till parametertypen för den valda operatorn och typen av resultat är operatorns returtyp. De fördefinierade unära 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.

Uppliftade (§12.4.8) former av de icke-uppliftade fördefinierade unära plusoperatörer som definieras ovan är också fördefinierade.

12.9.3 Unär negativ operator

För en operation av formen –x, tillämpas operatöröverbelastningslösning (§12.4.4) för att välja en specifik operatörsimplementering. Operand konverteras till parametertypen för den valda operatorn och typen av resultat är operatorns returtyp. De fördefinierade unary minus-operatorerna är:

  • Heltalsnegation

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

    Resultatet beräknas genom att subtrahera X från noll. Om värdet för X är det minsta representable-värdet av operandetypen (−2³¹ för int eller −2⁶³ för long), kan den matematiska negationen av X inte representeras inom operandtypen. Om detta inträffar inom en checked-kontekst kastas en System.OverflowException; om det inträffar inom en unchecked-kontekst är resultatet operandens värde och överflödet rapporteras inte.

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

    Om negationsoperatorns operand är av typen ulonguppstår ett kompileringsfel. Ett undantag är regeln som tillåter att long-värdet −9223372036854775808 (−2⁶³) skrivs som en decimal heltalsliteral (§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 NaNblir resultatet också NaN.

  • Negation av decimaltal

    decimal operator –(decimal x);
    

    Resultatet beräknas genom att subtrahera X från noll. Decimalnegation motsvarar att använda den unära minusoperatorn av typen System.Decimal.

Lyftas (§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 operation av formen !x, tillämpas operatöröverbelastningslösning (§12.4.4) för att välja en specifik operatörsimplementering. 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 operandens logiska negation: Om operanden är trueblir resultatet false. Om operand är falseblir resultatet true.

Lyfta former (§12.4.8) av den icke-lyfta fördefinierade logiska negationsoperatorn som definieras ovan är också fördefinierade.

Observera: De prefix logiska negations- och postfix null-förlåtande operatorerna (§12.8.9), även om de representeras av samma lexikala token (!), är distinkta. slutkommentar

12.9.5 Bitvis komplementoperator

För en operation av formen ~x, tillämpas operatöröverbelastningslösning (§12.4.4) för att välja en specifik operatörsimplementering. 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 det bitvisa komplementet av x.

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

E operator ~(E x);

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

Lyfta (§12.4.8) versioner av de olyfta fördefinierade bitvisa komplementsoperatorer 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 prefixinkrement- eller prefixdekrementoperation ska vara ett uttryck som klassificeras som en variabel, en egenskapsåtkomst eller en indexeringsåtkomst. Resultatet av åtgärden är ett värde av samma typ som operanden.

Om operanden för en prefixinkrement- eller prefixdekrementoperation är en egenskaps- eller indexerartillgång, ska egenskapen eller indexeraren ha både en 'get'- och en 'set'-accessor. Om så inte är fallet uppstår ett bindningstidsfel.

Unary operator overload resolution (§12.4.4) används för att välja en specifik operatorimplementering. Fördefinierade operatorer för ++ och -- finns för följande typer: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimaloch alla uppräkningstyper. De fördefinierade ++ operatorerna returnerar värdet som genereras genom att lägga till 1 till operanden, och de fördefinierade -- operatorerna returnerar värdet som genereras genom att subtrahera 1 från operanden. I en checked 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 finnas 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 en prefixoperation för inkrement eller dekrement 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 för x 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 åtkomst till en egenskap eller indexerare:
    • Instansuttrycket (om x inte är static) och argumentlistan (om x är en indexeringsåtkomst) som är associerad med x utvärderas, och resultaten används i efterföljande hämtnings- och inställningsanrop av accessor.
    • Anropet för get-återgivaren av x utförs.
    • 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-accessorn för x anropas med det här värdet som värdeargument.
    • Det här värdet blir också resultatet av åtgärden.

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

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

Lyfta (§12.4.8) former av de o-lyfta fördefinierade prefix-inkrement- och decrement-operatorer 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 formen (T)E, där T är en typ och E är en unary_expression, utför en explicit konvertering (§10.3) av värdet för E till typ 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 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 att skriva 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 med token är korrekt grammatik för en typ, och token omedelbart efter slutparenteserna är token "~", token "!", token "(", en identifierare (§6.4.3), en literal (§6.4.5), eller nyckelord (§6.4.4) utom 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 x.y korrekt grammatik för en typ, även om x.y faktiskt inte anger någon 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 är det inte, ä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_expression(eftersom ett sådant nyckelord eventuellt 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 får en await_expression inte förekomma på följande platser:

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

Note: En await_expression kan inte förekomma på de flesta platser inom en query_expressioneftersom de syntaktiskt omvandlas till att använda lambda-uttryck som inte är asynkrona. slutkommentar

I en asynkron funktion ska await inte användas som en available_identifier även om ordagrant identifierare @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 fungerar await som en normal identifierare.

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

12.9.8.2 Väntbara uttryck

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

  • t är av kompileringstidstyp dynamic
  • t har en tillgänglig instans eller tilläggsmetod som heter GetAwaiter utan parametrar och inga typparametrar, och en returtyp A för vilken alla följande villkor gäller:
    • 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 vänttyp för vänta-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 metoden INotifyCompletion.OnCompleted är att registrera en "fortsättning" på uppgiften. d.v.s. ett ombud (av typen System.Action) som anropas när uppgiften är klar.

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

12.9.8.3 Klassificering av inväntningsuttryck

Uttrycket await t klassificeras på samma sätt som uttrycket (t).GetAwaiter().GetResult(). Om returtypen för GetResult därför är voidklassificeras await_expression som ingenting. Om den har en icke-void returtyp Tklassificeras await_expression som ett värde av typen 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().
  • En 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 dynamicoch vid kompileringstillfället annars. Låt r beteckna återupptagandedelegaten (§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 är true), eller vid senare anrop av återupptagningsdelegaten (om b är false), utvärderas uttrycket (a).GetResult(). Om det returnerar ett värde är det värdet resultatet av await_expression. Annars är resultatet ingenting.

En inväntares implementering av gränssnittsmetoderna INotifyCompletion.OnCompleted och ICriticalNotifyCompletion.UnsafeOnCompleted bör leda till 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 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 kompileringstidstypen för uttrycket dynamic, och lösningen som beskrivs nedan sker vid körning med körningstypen för de operander som har kompileringstypen dynamic.

12.10.2 Multiplikationsoperator

För en åtgärd av 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.

  • Heltalsmultiplikation

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

    I en checked kontext kastas en System.OverflowException om produkten ligger utanför resultattypens intervall. I ett unchecked-kontext rapporteras inte spill, och signifikanta bitar i högre ordning utanför resultattypens intervall kastas bort.

  • Flyttalsmultiplikation

    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 är x och y positiva ändliga värden. z är resultatet av x * y, avrundat till närmaste värde som kan representeras. Om resultatet är för stort för måltypen är z oändlighet. På grund av avrundning kan z vara noll trots att 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 i flyttalstabellerna i §12.10.2§12.10.6 användningen av "+" innebär värdet 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ågra 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.

Lyfta (§12.4.8) former av de icke-lyfta fördefinierade multiplikationsoperatorerna som definieras ovan är också fördefinierade.

12.10.3 Divisionsoperatör

För en åtgärd av 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 för x 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 det minsta representerbara int eller long värde och den högra operanden är –1, uppstår en översvämning. I ett checked-kontekst leder detta till att en System.ArithmeticException (eller en underklass därav) kastas. I ett unchecked-sammanhang är det implementeringsdefinierat om en System.ArithmeticException (eller en underklass därav) kastas eller om överskridningen inte rapporteras och resultatet blir värdet av den vänstra operanden.

  • 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 är x och y positiva ändliga värden. z är resultatet av x / y, avrundat till närmaste värde som kan representeras.

    +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
  • Decimaldivision

    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 odefinierade fördefinierade uppdelningsoperatorerna som definieras ovan, är också fördefinierade.

12.10.4 Restoperator

För en åtgärd av 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- eller long-värdet och den högra operanden är –1genereras en System.OverflowException om och bara 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 är x och y 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 definitionen IEC 60559 (där n är det heltal som är 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 alla fall där x / y inte utlöser ett 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.

    Decimalrest är motsvarande att använda restoperatorn av typen System.Decimal.

    Note: Dessa regler säkerställer att resultatet för alla typer aldrig har motsatt tecken på den vänstra operanden. slutkommentar

Lyfta (§12.4.8) former av de olyfta fördefinierade restoperatorerna som definieras ovan är också fördefinierade.

12.10.5 Additionsoperator

För en åtgärd av 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
    

    I en checked-kontext, om summan ligger utanför resultattypens intervall, genereras en System.OverflowException. I ett unchecked-kontext rapporteras inte spill, och signifikanta bitar i högre ordning utanför resultattypens intervall kastas bort.

  • Flyttalsaddition:

    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 är x och y icke-nollfinita värden och z är resultatet av x + y. Om x och y har samma storlek men motsatta tecken är z positiv noll. Om x + y är för stor för att representeras 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 den binära + operatorn utför strängsammanfogning. Om en operand av strängkonkatenation är null, ersätts den med en tom sträng. Annars konverteras alla icke-string operander till dess strängrepresentation genom att anropa den virtuella ToString metod som ärvs 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 US-English 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 delegetyp tillhandahåller implicit följande fördefinierade operator, där D är delegetypen:

    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å är null). 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

Lyfta (§12.4.8) former av de icke-lyfta fördefinierade tilläggsoperatorerna som definieras ovan är också fördefinierade.

12.10.6 Subtraktionsoperator

För en åtgärd av 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 alla y från x.

  • Subtraktion av heltal

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

    I en checked-kontekst, om skillnaden ligger utanför resultattypens intervall, genereras en System.OverflowException. I ett unchecked-kontext rapporteras inte spill, och signifikanta bitar i högre ordning utanför resultattypens intervall kastas bort.

  • 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 är x och y icke-nollfinita värden och z är resultatet av x – y. Om x och y är lika är z positiv noll. Om x – y är för stor för att representeras 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 -y inlägg negation av y, inte att värdet är negativt.)

  • Decimalsubtraktion

    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 enumtyp tillhandahåller implicit följande fördefinierade operator, där E är enumtypen 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 för x och y, och typen av resultatet ä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.

  • Borttagning av delegering. Varje delegetyp tillhandahåller implicit följande fördefinierade operator, där D är delegetypen:

    D operator –(D x, D y);
    

    Semantiken är följande:

    • Om den första operanden är nullblir resultatet av åtgärden null.
    • 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 är lika, enligt vad som bestäms av delegeringens likhetsoperator (§12.12.9), är resultatet av operationen null.
      • 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 operationen värdet av 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

Lyfta (§12.4.8) former av de oolyfta 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 kompileringstidstypen för uttrycket dynamic, och lösningen som beskrivs nedan sker vid körning med körningstypen för de operander som har kompileringstypen dynamic.

För en åtgärd av 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.

    De höga bitarna utanför resultattypens intervall för x 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 rätt med ett antal bitar som beräknas enligt beskrivningen nedan.

    När x är av typen int eller longignoreras 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 om x är icke-negativ och anges till en om x är negativ.

    När x är av typen uint eller ulongignoreras de lågordningsbitar av 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 uintbestäms skiftantalet av de fem lägst ordnade bitarna i count. Med andra ord beräknas skiftantalet från count & 0x1F.
  • När typen av x är long eller ulonganges skiftantalet av de sex lägsta bitarna av count. Med andra ord beräknas skiftantalet från count & 0x3F.

Om det resulterande skiftantalet är noll returnerar skiftoperatorerna helt enkelt värdet för x.

Skiftåtgärder orsakar aldrig spill och ger samma resultat i markerade och avmarkerade kontexter.

När den vänstra operanden i >>-operatorn är av en signerad integraltyp utför operatorn en aritmetik skift höger där värdet för operandens viktigaste bit (teckenbiten) sprids till tomma bitpositioner i hög ordning. När den vänstra operanden i >>-operatorn är av en osignerad integraltyp utför operatorn en logisk skifta åt 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 int, utför åtgärden unchecked ((int)((uint)x >> y)) en logisk högerförskjutning för x. slutexempel

Lyfta (§12.4.8) former av de olyfta 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 operatorerna för relations- och typtestning.

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
    ;

Not: Uppslagning för den högra operanden av is-operatören måste först testas som en typ, och sedan som ett uttryck som kan omfatta flera token. Om operand är ett uttryckmåste mönsteruttrycket ha lika hög eller högre prioritet som skiftuttryck. slutkommentar

is operatören beskrivs i §12.12.12 och as operatören beskrivs i §12.12.13.

Operatorerna ==, !=, <, >, <= och >= är jämförelseoperatorer.

Om en default_literal (§12.8.21) används som operande av en <, >, <=eller >= operator uppstår ett kompileringsfel. Om en default_literal används som båda operanderna för en ==- eller != operator uppstår ett kompileringsfel. Om en default_literal används som vänster operand för is- eller as-operatorn 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 kompileringstidstypen för uttrycket dynamic, och lösningen som beskrivs nedan sker vid körning med hjälp av körningstypen för de operander som har kompileringstidstypen dynamic.

För en drift av formuläret x «op» y, där «op» är en jämförelseoperator, tillämpas överbelastningsupplösning (§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 null literal utförs inte överlagringsupplösningen och uttrycket utvärderas till ett konstant värde på true eller false beroende på om operatorn är == eller !=.

De fördefinierade jämförelseoperatorerna beskrivs i följande underavsnitt. 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 yfalse annars
x != y true om x inte är lika med yfalse annars
x < y true om x är mindre än yfalse annars
x > y true om x är större än yfalse annars
x <= y true om x är mindre än eller lika med yfalse annars
x >= y true om x är större än eller lika med yfalse 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) former av de odefinierade fördefinierade heltalsjämförelseoperatorer 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 resultatet false för alla operatorer utom !=, för vilka resultatet true. För två operander ger x != y alltid samma resultat som !(x == y). Men när en eller båda operanderna är NaN ger <, >, <=och >= operatorerna inte samma resultat som den logiska negationen för den motsatta operatorn.

Exempel: Om någon av x och y är NaN är x < yfalse, 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.

Upphöjda (§12.4.8) former av de icke-upphöjda fördefinierade flyttalsjämförelseoperatorerna som tidigare har fördefinierats är också fördefinierade.

12.12.4 Decimalt 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å decimalopereringarna 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.

Upphöjda (§12.4.8) former av de opåverkade redan definierade decimaljämförelseoperatorerna som definieras ovan är också redan definierade.

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 resultatet false.

Resultatet av != är false om både x och y är true eller om både x och y är false. Annars blir resultatet true. När operanderna är av typen boolgenererar operatorn != samma resultat som operatorn ^.

Lyfta (§12.4.8) former av de fördefinierade o-lyfta booleska likhetsoperatorerna definierade 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 av 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 utvärdera ((U)x) «op» ((U)y). Med andra ord jämför jämförelseoperatorerna för uppräkningstypen helt enkelt de underliggande integralvärdena för de två operanderna.

Lyfta (§12.4.8) former av de olyfta fördefinierade enummerationsjämförelseoperatorer som nämns ovan är också fördefinierade.

12.12.7 Likhetsoperatorer för referenstyper

Varje klasstyp C tillhandahåller implicit följande fördefinierade operatorer för likhet mellan 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 båda är null, medan operator != returnerar true om och bara om operator == med samma operander skulle returnera false.

Förutom normala tillämplighetsregler (§12.6.4.2) kräver de fördefinierade referenstypjämlikhetsoperatorerna 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 som en reference_type eller literalen null. Dessutom finns en identitets- eller explicit referenskonvertering (§10.3.5) från antingen operand till den andra operandens typ.
  • En operand ä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 inte har begränsningen för värdetyp.
    • Om T vid körningen är en värdetyp som inte kan nollföras blir resultatet av ==false och resultatet av !=true.
    • Om vid körning T är en nullbar värdetyp beräknas resultatet från operandens HasValue egenskap enligt beskrivningen i (§12.12.10).
    • Om T vid körning är en referenstyp blir resultatet true om operanden är nulloch 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 referenstypsoperatorerna 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 operation i formen x == y eller x != y, om det finns någon tillämplig användardefinierad operator == eller operator !=, kommer operatorns regler för överlagringslösning (§12.4.5) att välja den operatorn istället för den fördefinierade likhetsoperatorn för referenstyper. Det är alltid möjligt att välja den fördefinierade referenstypjämlikhetsoperatorn genom att uttryckligen kasta en eller båda operanderna till typen object.

slutkommentar

Exempel: I det följande exemplet kontrolleras om ett argument för en obegränsad parametertyp är null.

class C<T>
{
   void F(T x)
   {
      if (x == null)
      {
          throw new ArgumentNullException();
      }
      ...
   }
}

Den x == null konstruktionen är tillåten även om T kan representera en värdetyp som inte kan nulleras, och resultatet definieras helt enkelt som false när T är en värdetyp som inte kan upphävas.

slutexempel

För en operation av formen x == y eller x != y, om det finns några tillämpliga operator == eller operator !=, väljer överlagringsresolution för operatorer (§12.4.5) den operatorn i stället för den fördefinierade referenstypslikhetsoperatorn.

Note: Det är alltid möjligt att välja den fördefinierade referenstypers likhetsoperator genom att uttryckligen kasta båda operanderna till typen 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 ger True eftersom den fördefinierade strängjämförelseoperatorn (§12.12.8) väljs när båda operanderna är av typen string. Alla återstående jämförelser ger utdata False eftersom överbelastningen av typen operator == i string inte är tillämplig om 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);
    }
}

Producerar False eftersom typkonverteringar skapar referenser till två separata instanser av inneslutna 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-null referenser 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.7kan referenstypsjämlikhetsoperatorerna 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 delegeringsjämlikhet är:

bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y);

Två delegatinstanser anses vara likvärdiga på följande sätt:

  • Om någon av de delegerade instanserna är nullär de lika om och endast om båda är null.
  • Om delegaterna har olika körtyper är de inte lika.
  • Om båda de delegerade instanserna har en anropslista (§20.2), är dessa instanser lika om och endast om deras anropslistor är samma längd, 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.
  • Poster i anropslistan som tagits fram från utvärderingen av semantiskt identiska anonyma funktioner (§12.19) med samma (eventuellt tomma) uppsättning av instanser av yttre variabler är tillåtna (men inte ett krav) att vara lika.

Om operatörens överbelastningsmatchning matchar antingen delegeras likhetsoperator och bindningstidstyperna för båda operanderna är delegattyper enligt §20 i stället för System.Delegate, och det inte finns någon identitetskonvertering mellan bindningstidens operandtyper, uppstår ett bindningstidsfel.

Obs: Den här regeln förhindrar jämförelser som aldrig kan betrakta icke-null värden 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 den null literalen, även om det inte finns någon fördefinierad eller användardefinierad operator (i obelyft eller upplyft form) för åtgärden.

För en operation av en av formerna

x == null    null == x    x != null    null != x

om x är ett uttryck av en nullbar värdetyp, om operatorns överbelastningsupplösning (§12.4.5) inte kan hitta en tillämplig operator, beräknas resultatet i stället från egenskapen HasValue för x. Mer specifikt översätts de två första formulären till !x.HasValueoch 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 operand x och y för en ==- eller !=-operator klassificeras antingen som en tuppel eller som ett värde med en tuppeltyp (§8.3.11), kommer operatorn att vara en tuppellikhetsoperator.

Om en operande e klassificeras som en tuppel ska elementen e1...en vara resultatet av att utvärdera elementuttrycken i tuppelexpressionen. Annars, om e är ett värde av typen tuppel, ska elementen vara t.Item1...t.Itemn där t är resultatet av att utvärdera e.

Operanderna x och y för en tupplars likhetsoperator ska ha samma aritet, eller så uppstår ett kompileringstids-fel. 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 true och false operatorer.

Tuppelns likhetsoperator x == y utvärderas på följande sätt:

  • Den vänstra operanden x utvärderas.
  • Den högra operanden y 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 är det resultatet.
      • Om jämförelsen annars gav en dynamic anropas operatorn false dynamiskt på den, och det resulterande bool värdet negeras med operatorn för logisk negation (!).
      • 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 operatorn för logisk negation (!).
    • Om den resulterande bool är falsesker ingen ytterligare utvärdering och resultatet av likhetsoperatorn för tupler är false.
  • Om alla elementjämförelser gav true, blir resultatet av tuplens likhetsoperator true.

Tuppelns likhetsoperator x != y utvärderas på följande sätt:

  • Den vänstra operanden x utvärderas.
  • Den högra operanden y 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 ä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 den resulterande bool är truesker ingen ytterligare utvärdering och resultatet av likhetsoperatorn för tupler är true.
  • Om alla elementjämförelser gav false, blir resultatet av tuplens likhetsoperator false.

12.12.12 Is-operatorn

Det finns två former av is-operatorn. Den ena är operatorn är av typen, som har en typ till höger. ** Den andra operatorn är is-pattern, som har ett mönster på högersidan.

12.12.12.1 Operatorn är av typen

Operatorn av typen används för att kontrollera om ett objekts körningstyp ä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 typ annan än dynamic, är ett booleskt värde som anger om E är icke-null och kan konverteras till typ T genom en referenskonvertering, en boxningskonvertering, en avboxningskonvertering, en omslutande konvertering eller en omkastningskonvertering.

Å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 null literal, eller om värdet för E är null, blir resultatet false.
  3. Annars:
  4. Låt R vara körningstyp för E.
  5. Låt D härledas från R på följande sätt:
  6. Om R är en nullbar värdetyp är D den underliggande typen av R.
  7. Annars är DR.
  8. Resultatet beror på D och T på följande sätt:
  9. Om T är en referenstyp, är resultatet true om så är fallet:
    • 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 det finns en boxningskonvertering från D till T.
      Eller: D är en värdetyp och T är en gränssnittstyp som implementeras av D.
  10. Om T är en nullbar värdetyp, är resultatet true om D är den underliggande typen av T.
  11. Om T är en värdetyp som inte kan nulleras true resultatet om D och T är av samma typ.
  12. Annars blir resultatet false.

Användardefinierade konverteringar beaktas inte av is-operatorn.

Obs: Eftersom is operatorn utvärderas vid körningen har alla typargument ersatts och det inte finns några öppna typer (§8.4.3) att överväga. slutkommentar

Obs: Operatorn is kan förstås i termer av kompileringstidstyper och konverteringar enligt följande, där C är kompileringstidstypen för E:

  • Om kompileringstidstypen för e är samma som T, eller om en implicit referenskonvertering (§10.2.8), boxningskonvertering (§10.2.9), isoleringskonvertering (§10.6), eller en explicit öppningskonvertering (§10.6) finns från kompileringstidstypen av E till T:
    • Om C är av en värdetyp som inte kan vara null, är resultatet av åtgärden true.
    • Annars motsvarar resultatet av operationen att utvärdera E != null.
  • I annat fall, om en explicit referenskonvertering (§10.3.5) eller en explicit unboxing-konvertering (§10.3.7) finns från C till T, eller om C eller T är en öppen typ (§8.4.3), ska körningskontroller som ovan utföras.
  • Annars är ingen referens-, boxnings-, omslutnings- eller omskrivningskonvertering av E till 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

Operatorn is-pattern används för att kontrollera om värdet som beräknas av ett uttryck matchar ett angivet 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 av formen E is P, där E är ett relationsuttryck av typen T och P är ett mönster, är det ett kompileringstidfel om något av följande gäller:

  • 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 gjutet uttryck (§12.9.7) utlöser as operatorn aldrig ett undantag. Om den angivna konverteringen inte är möjlig är det resulterande värdet i stället null.

I en åtgärd i formuläret E as Tska E vara ett uttryck och T ska 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 mellan E och T.
  • Typen av E eller T är en öppen typ.
  • E är null bokstavligt talat.

Om kompileringstidstypen för E inte är dynamicgenererar å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. En kompilator kan förväntas optimera E as T för att utföra högst en körningstypkontroll i stället för de två körningstypkontrollerna som antyds i expansionen ovan.

Om kompileringstidstypen för E är dynamic, till skillnad från gjutningsoperatorn är as operatorn 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 av G är känd för att vara en referenstyp, eftersom den har klassbegränsningen. Typparametern U av H är dock inte; därför tillåts inte användningen av as-operatorn 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 kompileringstidstypen för uttrycket dynamic, och lösningen som beskrivs nedan sker vid körning med hjälp av körningstypen för de operander som har kompileringstidstypen dynamic.

För en drift av formuläret x «op» y, där «op» är en av de logiska operatorerna, tillämpas överbelastningsupplösning (§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 underavsnitt.

12.13.2 Heltalslogiska 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);

Den & operatorn beräknar det bitvis logiska OCH för de två operanderna beräknar |-operatorn den bitvis logiska OR:en för de två operanderna, och ^-operatorn beräknar den bitvis logiska exklusiva OR:en för de två operanderna. Inga överflöden är möjliga från dessa operationer.

Lyfta (§12.4.8) versioner av de ej lyfta fördefinierade heltaliga 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 av x «op» y, där x och y är uttryck av en uppräkningstyp E med en underliggande typ Uoch «op» är en av de logiska operatorerna, är exakt samma som att utvärdera (E)((U)x «op» (U)y). Med andra ord utför uppräkningstypen logiska operatorer helt enkelt den logiska åtgärden på den underliggande typen av de två operanderna.

Lyfta (§12.4.8) former av de o-lyfta fördefinierade uppräknade 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 resultatet false.

Resultatet av x | y är true om antingen x eller y är true. Annars blir resultatet false.

Resultatet av x ^ y är true om x är true och y är falseeller x är false och y är true. Annars blir resultatet false. När operanderna är av typen boolberäknar ^-operatorn samma resultat som operatorn !=.

12.13.5 Nullable Boolesk & och | operatörer

Den null-bar booleska typen bool? kan representera tre värden: true, falseoch null.

Precis som med de andra binära operatorerna är de upplyfta formerna av de logiska operatorerna & och | (§12.13.4) också fördefinierade:

bool? operator &(bool? x, bool? y);
bool? operator |(bool? x, bool? y);

Semantiken för operatorerna lifted & och | 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

Note: Den bool? typen liknar den trevärdestyp som används för booleska uttryck i SQL. Tabellen ovan följer samma semantik som SQL, medan tillämpning av reglerna i §12.4.8 för & och | operatörer inte skulle göra det. Reglerna i §12.4.8 tillhandahåller redan SQL-liknande semantik för den lyfta ^ 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 som "kortsluter".

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 är false.
  • Åtgärden x || y motsvarar åtgärden x | y, förutom att y endast utvärderas om x inte är true.

Observera: Anledningen till att kortslutning sker genom villkoren "inte sant" och "inte falskt" är att möjliggöra för användardefinierade villkorsoperatorer att definiera när kortslutning ska tillämpas. 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 || kortsluta. 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 kompileringstidstypen för uttrycket dynamic, och lösningen som beskrivs nedan sker vid körning med hjälp av körningstypen för de operander som har kompileringstidstypen dynamic.

En operation av formen x && y eller x || y bearbetas genom tillämpning av överbelastningsupplösning (§12.4.5) som om operationen hade skrivits x & y eller x | y. Då

  • Om överlagringsupplösningen misslyckas med att hitta en bästa operator, eller om den väljer en av de fördefinierade logiska heltalsoperatorerna eller nullbara booleska logiska operatorerna (§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.
  • Annars är den valda operatorn en användardefinierad operator 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 i && 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 på följande sätt:

  • Åtgärden x && y utvärderas som x ? y : false. Med andra ord utvärderas x först och konverteras till typen bool. Om x sedan är trueutvärderas y och konverteras till typen booloch detta blir resultatet av åtgärden. Annars är resultatet av åtgärden false.
  • Åtgärden x || y utvärderas som x ? true : y. Med andra ord utvärderas x först och konverteras till typen bool. Om x sedan är trueblir resultatet av åtgärden true. Annars utvärderas och konverteras y till typen booloch detta blir resultatet av åtgärden.

12.14.3 Användardefinierade villkorsstyrda logiska operatorer

När operanderna i && eller || är av typer som deklarerar en tillämplig användardefinierad operator & eller operator |, ska båda 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 Toch 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 användardefinierade operator true eller operator false med den valda användardefinierade operatorn:

  • Åtgärden x && y utvärderas som T.false(x) ? x : T.&(x, y), där T.false(x) är ett anrop av operator false som deklareras i Toch T.&(x, y) är ett anrop av den valda operator &. Med andra ord utvärderas x 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 utvärderas y och den valda operator & anropas på värdet som tidigare beräknats för x och värdet som beräknas för y för att generera resultatet av åtgärden.
  • Åtgärden x || y utvärderas som T.true(x) ? x : T.|(x, y), där T.true(x) är ett anrop av operator true som deklareras i Toch T.|(x, y) är ett anrop av den valda operator |. Med andra ord utvärderas x först och operator true anropas på resultatet för att avgöra om x är definitivt sant. Om x är definitivt sant är resultatet av åtgärden det värde som tidigare beräknats för x. Annars utvärderas y och den valda operator | anropas på värdet som tidigare beräknats för x och värdet som beräknas för y för att generera resultatet av åtgärden.

I någon av dessa operationer utvärderas uttrycket som ges av x bara en gång, och uttrycket som ges av y utvärderas antingen inte alls eller exakt en gång.

12.15 Null-sammanslagningsoperatorn

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 av formen a ?? b, om a inte ärnull, är resultatet a; annars blir resultatet b. Åtgärden utvärderar endast b 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 av formen a ?? b ?? c utvärderas som a ?? (b ?? c). I allmänna termer returnerar ett uttryck för formuläret E1 ?? E2 ?? ... ?? EN den första operander som inte ärnull, eller null om alla operander är null. slutexempel

Vilken typ av uttryck a ?? b beror på vilka implicita konverteringar som är tillgängliga på operanderna. I prioritetsordning är typen av a ?? bA₀, A, eller B, där A är typen av a (förutsatt att a har en typ), B är typen av b(förutsatt att b har en typ), och A₀ är den underliggande typen av A om A är en nullbar värdetyp eller A annat. Mer specifikt bearbetas a ?? b på följande sätt:

  • Om A finns och inte är en nullbar värdetyp eller en referenstyp uppstår ett kompileringsfel.
  • Om A finns och b är ett dynamiskt uttryck är resultattypen annars dynamic. Vid körning utvärderas a först. Om a inte är nullkonverteras a till dynamicoch detta blir resultatet. Annars utvärderas b och detta blir resultatet.
  • Annars, om A finns och är en nullbar värdetyp och det finns en implicit konvertering från b till A₀, är resultattypen A₀. Vid körning utvärderas a först. Om a inte är nullbehandlas a som typ A₀, och detta blir resultatet. Annars utvärderas b och konverteras till typen A₀, och detta blir resultatet.
  • Annars är resultattypen Aom b finns och det finns en implicit konvertering från A till A. Vid körning utvärderas a först. Om a inte är nullblir a resultatet. Annars utvärderas b och konverteras till typen A, och detta blir resultatet.
  • Annars, om A finns och är en nullbar värdetyp, b har en typ B och det finns en implicit konvertering från A₀ till B, är resultattypen B. Vid körning utvärderas a först. Om a inte är nulloch a packas upp till typ A₀ och konverteras till typ B, vilket blir resultatet. Annars utvärderas b och blir resultatet.
  • Annars, om b har en typ B och det finns en implicit konvertering från a till B, är resultattypen B. Vid körning utvärderas a först. Om a inte är nullkonverteras a till typ Boch detta blir resultatet. Annars utvärderas b och blir resultatet.

Annars är a och b inkompatibla och ett kompileringsfel inträffar.

12.16 Operatorn för utkastsuttryck

throw_expression
    : 'throw' null_coalescing_expression
    ;

En throw_expression kastar värdet som skapas genom att utvärdera null_coalescing_expression. Uttrycket ska implicit konverteras till System.Exceptionoch resultatet av utvärderingen av uttrycket konverteras till System.Exception innan det utlöses. Beteendet vid körningen av utvärderingen av ett throw-uttryck är detsamma som specificerat för en throw-instruktion (§13.10.6).

En throw_expression har ingen typ. En throw_expression kan konverteras till varje typ genom en implicit throw-konvertering.

Ett utkastsuttryck ska endast förekomma i följande syntaktiska kontexter:

  • Som den andra eller tredje operanden för en ternär villkorsoperator (?:).
  • Som den andra operanden för en null coalescing-operatör (??).
  • Som kroppen av en lambda eller medlem med uttrycksbaserad kropp.

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 _ ett enkelt 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 outargument_value i en argument_list.
  • Som en enkel kassera _ bestående 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.

Note: Det innebär att ett deklarationsuttryck inte kan parenteseras. slutkommentar

Det är ett fel att en implicit typvariabel som deklareras med en declaration_expression refereras inom argument_list där den deklareras.

Det är ett fel att en variabel som deklarerats med en declaration_expression refereras till inom en dekonstruktionstilldelning där den förekommer.

Ett deklarationsuttryck som är en enkel borttagning eller där local_variable_type är identifieraren var klassificeras som en implicit typad variabel. 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 på vänstersidan av en enkel tilldelning är den härledda typen av variabel typen på högersidan av tilldelningen.
  • I en tuple_expression på vänster sida av en enkel tilldelning är den härledda typen av variabel typen av det motsvarande tupelelementet på höger sida (efter att tilldelningen dekonstruerats).

Annars klassificeras deklarationsuttrycket som en uttryckligen typad variabel, och uttryckets typ samt den deklarerade variabeln ska vara den som anges av lokal_variabel_typ.

Ett deklarationsuttryck med identifieraren _ är ett ignorerande (§9.2.9.2), och introducerar 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 av s1 visar både explicita och implicit inskrivna deklarationsuttryck. Den härledda typen av b1 är bool eftersom det är typen av motsvarande utdataparameter i M1. Efterföljande WriteLine kan komma åt i1 och b1, som har införts i den omgivande omfattningen.

Deklarationen av s2 visar ett försök att använda i2 i det kapslade anropet till M, vilket inte är tillåtet, 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 listan över kapslade argument där b2 deklarerades.

Deklarationen av s3 visar användningen av både implicit och explicit typade deklarationsuttryck som ignoreras. Eftersom underskott inte deklarerar en namngiven variabel, är flera förekomster av identifieraren _ tillåtna.

(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 _);
}

Det här exemplet visar användningen av var _ för att tillhandahålla en implicit typad bortkastad när _ inte är tillgänglig, eftersom den anger en variabel i det omgivande omfånget.

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 kastuttryck (§12.16) tillåts inte i en villkorsoperator om ref finns.

Ett villkorsuttryck för formuläret b ? x : y utvärderar först villkoret b. Om b sedan är trueutvärderas x och blir resultatet av åtgärden. Annars utvärderas y och blir resultatet av operationen. 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 av formen a ? b : c ? d : e utvärderas som a ? b : (c ? d : e). slutexempel

Den första operanden för ?:-operatorn ska vara ett uttryck som implicit kan konverteras till booleller 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_referenceoch resultatets typ kan vara av båda typerna. Om någon av typerna är dynamicföredrar typinferens dynamic (§8.7). Om någon av typerna är en tuple (§8.3.11), innehåller typinferens elementnamnen när elementnamnen i samma ordningsplacering matchar i båda tuplarna.
  • 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 kontrollerar den andra och tredje operanderna, x och y, för ?: operatorn typen av villkorsuttryck:

  • Om x har typ X och y har typ Y
    • Om det finns en identitetskonvertering mellan X och Yär resultatet den bästa gemensamma typen av en mängd uttryck (§12.6.3.15). Om någon av typerna är dynamicföredrar typinferens dynamic (§8.7). Om någon av typerna är en tuple (§8.3.11), innehåller typinferens elementnamnen när elementnamnen i samma ordningsplacering matchar i båda tuplarna.
    • Om det annars finns en implicit konvertering (§10.2) från X till Y, men inte från Y till X, är Y typen av villkorsuttryck.
    • Annars, om det finns en implicit uppräkningskonvertering (§10.2.4) från X till Y, är Y typen av villkorsuttryck.
    • Annars, om det finns en implicit uppräkningskonvertering (§10.2.4) från Y till X, är X typen av villkorsuttryck.
    • Om det annars finns en implicit konvertering (§10.2) från Y till X, men inte från X till Y, är X 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 konverteras till den typen, är det typen av villkorsuttryck.
  • Annars kan ingen uttryckstyp fastställas och ett kompileringsfel inträffar.

Körningstiden för ett ref-villkorsuttryck av formen b ? ref x : ref y består av följande steg:

  • Först, utvärderas b, och sedan bestäms bool-värdet för b.
    • Om det finns en implicit konvertering från typen av b till bool utförs den implicita konverteringen för att skapa ett bool värde.
    • Annars anropas operator true som definieras av typen b för att skapa ett bool värde.
  • Om det bool värde som skapas av steget ovan är trueutvärderas x och den resulterande variabelreferensen blir resultatet av villkorsuttrycket.
  • Annars utvärderas y och den resulterande variabelreferensen blir resultatet av villkorsuttrycket.

Körningsbearbetningen av ett villkorsuttryck av typen b ? x : y består av följande steg:

  • Först, utvärderas b, och sedan bestäms bool-värdet för b.
    • Om det finns en implicit konvertering från typen av b till bool utförs den implicita konverteringen för att skapa ett bool värde.
    • Annars anropas operator true som definieras av typen b för att skapa ett bool värde.
  • Om det bool värde som skapas av steget ovan är trueutvärderas och konverteras x till typen av villkorsuttryck, och detta blir resultatet av villkorsuttrycket.
  • Annars utvärderas och konverteras y 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 "infogad" 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 delegatvärde som refererar till metoden 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 smaker 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 uttryck alternativ är tillämpliga, skall 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 uttryckskulle ett syntaktiskt formulär som x?.M() vara ett fel om resultattypen för M är void (§12.8.13). Men när den betraktas som en null_conditional_invocation_expressiontillåts resultattypen vara void. slutkommentar

Exempel: Resultattypen för 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 modifierare ä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 delegattyp 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 en delegat med en parameterlista som inte innehåller utdataparametrar.

Ett block kropp för 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_expressiontillåter att parameterlistan utelämnas helt och hållet, vilket ger konverteringsbarhet för att delegera typer av valfri lista med värdeparametrar.
  • lambda_expressiontillå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_expressions har konverteringar till kompatibla uttrycksträdstyper (§8.6).

12.19.2 Anonyma funktionssignaturer

Den anonymous_function_signature av en anonym funktion definierar namnen och eventuellt typerna av parametrarna för den anonyma funktionen. Omfattningen av parametrarna för den anonyma funktionen är anonymous_function_body (§7.7). Tillsammans med parameterlistan (om tillämpligt) utgör den anonyma metodkroppen ett deklarationsutrymme (§7.3). Det är alltså ett kompileringsfel om namnet på en parameter i den anonyma funktionen matchar namnet på en lokal variabel, lokal konstant eller parameter vars omfång inkluderar anonymous_method_expression eller lambda_expression.

Om en anonym funktion har en explicit_anonymous_function_signaturebegränsas uppsättningen med kompatibla ombudstyper och uttrycksträdstyper till dem 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_signaturebegrä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

Huvuddel (uttryck eller block) 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 funktionen att komma åt en parameter av typen ref struct.
  • När typen av this är en structtyp är det ett kompileringsfel om koden försöker komma åt this. Detta gäller om åtkomsten är explicit (som i this.x) eller implicit (som i x där x är en instansmedlem i 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.
  • Kroppen har tillgång till de yttre variablerna (§12.19.6) av den anonyma funktionen. Åtkomsten till en yttre variabel refererar till instansen av variabeln som är aktiv när 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 i en innesluten anonym funktion.
  • En return-instruktion i brödtexten returnerar kontrollen från ett anrop av den närmaste omslutande anonyma funktionen, 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. I synnerhet kan en kompilator 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 överlagringslösning. 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 antingen vara en int eller en double och den resulterande summan är också antingen en int eller en double.

De Sum metoderna 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( ... )
    {
        ...
    }
}

Vid den första anropet av orderDetails.Sumär båda metoderna Sum giltiga eftersom den anonyma funktionen d => d.UnitCount är kompatibel med både Func<Detail,int> och Func<Detail,double>. Överbelastningslösningen 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 metoden Sum för 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

Varje lokal variabel, värdeparameter eller parametermatris vars omfång omfattar lambda_expression eller anonymous_method_expression kallas för en yttre variabel av 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 Fångade 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.1). 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 tas om hand av den anonyma funktionen och livslängden för x förlängs, åtminstone tills delegeringen 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 resultatet av 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 till en infångade yttre variabel kan inte tas.

Obs: Till skillnad från en okapslad variabel kan en fångad lokal variabel samtidigt exponeras för flera trå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 till exempel 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 av 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 en kompilator 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: Till 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 delegaterna fångar samma instans av x men separata instanser av y, och resultatet ä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 delegattyp D eller ett uttrycksträd E, antingen direkt eller genom att utföra ett uttryck för delegate-skapande new D(F). Denna konvertering avgör resultatet av den anonyma funktionen enligt beskrivningen i §10.7.

12.19.8 Implementeringsexempel

Det här underavsnittet är informativt.

Det här delavsnittet beskriver en möjlig implementering av omvandling av anonyma funktioner med avseende på 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 det här underavsnittet ger 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 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 i this:

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 åtkomst till 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ängd:

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 för 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 "lyfta" lokala variablerna kan delas mellan delegater och uttrycksträd.

Slut på informativ text.

12.20 Frågeuttryck

12.20.1 Allmänt

Frågeuttryck tillhandahålla 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, where, join eller 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 i en annan sekvens, vilket ger matchande par. Varje orderby-sats ordnar om objekt enligt angivna villkor. Den sista select- eller group-satsen anger resultatets form när det gäller 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, orderby, select och 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. I det här syftet är ett frågeuttryck ett uttryck som börjar med "fromidentifierare" följt av valfri token förutom ";", "=" 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, SelectMany, Join, GroupJoin, OrderBy, OrderByDescending, ThenBy, ThenByDescending, 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 utförandet 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 infogar intervallvariabler med transparenta identifierare markerade med *. Dessa beskrivs ytterligare i §12.20.3.8.

12.20.3.2 Sökfrågor 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 vilken är

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 Degenererade 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 på 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 from-sats:

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 from-sats:

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 customers
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 from-sats:

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 ett anrop av OrderByDescending eller ThenByDescending i stället.

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, where, join- eller orderby-satser och inte mer än en inledande from-sats i varje frågeuttryck.

12.20.3.6 Välj klausuler

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 identifier förekommer som en parameter i en anonym funktion, är medlemmarna av den associerade anonyma typen automatiskt i omfånget i kroppen av den anonyma funktionen.
  • När en medlem med en transparent identifierare finns i omfånget, finns även dess medlemmar i omfånget.
  • När en transparent identifierare förekommer som medlemsdeklarator i en anonym objektinitialisering 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 dessutom översätts 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 reduceras ytterligare 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

Mönstret för frågeuttryck upprättar ett metodmönster 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 med följande klassdefinition. Medlemmarna och tillgängliga extensonmetoder kallas "formen" av en allmän 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.

Note: Den rekommenderade relationen mellan C<T> och O<T> som säkerställer att metoderna ThenBy 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

Note: 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 innehå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, kan det vara egenskapsaccess, indexeraccess, händelseaccess eller en tuppel. 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 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 vänstra operanden för den enkla tilldelningsoperatorn får inte vara en händelseåtkomst, med undantag för vad som beskrivs i §15.8.2. Den enkla tilldelningsoperatören beskrivs i §12.21.2.

Operatorn = ref kallas referenstilldelningsoperatorn. Det gör det högra operanden, som ska vara en variable_reference (§9.5), till referenten till referensvariabeln som definieras av den vänstra operanden. Referenstilldelningsoperatören beskrivs i §12.21.3.

Andra tilldelningsoperatorer än operatorerna = och = ref kallas sammansatta tilldelningsoperatorer. 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 tilldelningsoperatorerna beskrivs i §12.21.4.

Operatorerna += och -= med ett händelseåtkomstuttryck som den vänstra operanden kallas händelsetilldelningsoperatorer. Ingen annan tilldelningsoperator är giltig med en händelseåtkomst som den vänstra operanden. Händelsetilldelningsoperatorerna 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 av formen 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 formatet E.P eller E[Ei] där E har kompileringstidstypen dynamic, är tilldelningen dynamiskt bunden (§12.3.3). I det här fallet är kompileringstidstypen för tilldelningsuttrycket dynamic, och lösningen som beskrivs nedan sker vid körning baserat på körningstypen för E. Om den vänstra operanden är av formuläret E[Ei] där minst ett element i Ei har kompileringstidstypen dynamicoch kompileringstidstypen för 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 tuppel kallas också en dekonstruerande tilldelning. Om något av tuppelns element i den vänstra operanden har ett elementnamn uppstår ett kompileringsfel. Om något av vänsteroperandens tupplelement är en declaration_expression och något annat element inte är en declaration_expression eller en enkel discard, uppstår ett kompileringsfel.

Typen av enkel tilldelning x = y är typen av en tilldelning till x av y, som bestäms rekursivt 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 av yi har typen Ti, har tilldelningen typen (T1, ..., Tn).
  • Om x annars klassificeras som en variabel är variabeln inte readonly, x har en typ Toch y har en implicit konvertering till Thar tilldelningen typen T.
  • Om x annars klassificeras som en implicit typvariabel (dvs. ett implicit skrivet deklarationsuttryck) och y har en typ T, är variabelns härledda typ Toch tilldelningen har typen T.
  • Om x klassificeras som en egenskap eller som ett indexerarattribut, och egenskapen eller indexeraren har en tillgänglig åtkomstmetod för att sätta värde, x har typen T, och y har en implicit konvertering till T, då har tilldelningen typen T.
  • Annars är tilldelningen inte giltig och ett bindningstidsfel inträffar.

Exekveringen vid körning av en enkel tilldelning i formen x = y med typen T utförs som en tilldelning till x av y med typen T, och består av följande rekursiva steg:

  • x utvärderas om det inte redan har utvärderats.
  • Om x klassificeras som en variabel utvärderas y och konverteras vid behov till T genom en implicit konvertering (§10.2).
    • Om variabeln som anges av x är ett matriselement i en reference_typeutförs en körningskontroll för att säkerställa att värdet som beräknas för y är kompatibelt med matrisinstansen där x är ett element. Kontrollen lyckas om y är null, eller om en implicit referenskonvertering (§10.2.8) finns från den typ av instans som refereras av y till den faktiska elementtypen för matrisinstansen som innehåller x. Annars utlöses 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 åtkomst till en egenskap eller indexerare:
    • y utvärderas och konverteras vid behov till T genom en implicit konvertering (§10.2).
    • Set-accessorn för x anropas med värdet som är resultatet av utvärderingen och konverteringen av y som dess värdeargument.
    • Värdet som är resultatet av utvärderingen och konverteringen av y returneras som ett resultat av tilldelningen.
  • Om x klassificeras som tuplen (x1, ..., xn) med aritet n.
    • y dekonstrueras med n element till ett tupppeluttryck e.
    • en resultattuple t skapas genom att konvertera e till T med hjälp av en implicit tuppelkonvertering.
    • För varje xi i ordning från vänster till höger utförs en tilldelning till xi av t.Itemi, dock utvärderas inte xi igen.
    • t returneras som ett resultat av tilldelningen.

Obs!: om kompileringstidstypen för x är dynamic, och det finns en implicit omvandling från kompileringstidstypen för y till dynamic, krävs ingen körningslösning. slutkommentar

Obs: Matrisens samavvikelseregler (§17.6) tillåter ett värde av en matristyp A[] vara 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 orsakar 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.7gä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 referenstilldelning operator.

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 kompileringsfel om ref-safe-context (§9.7.2) av 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 (d.v.s. 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 är skrivbar om den vänstra operanden är skrivbar.

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 att = ref är operatorn och b ? ref x : ref y den högra operanden: ref int a = ref (b ? ref x : ref y);. Det är viktigt att uttrycket ref binte 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 i en sammansatt tilldelning är av formatet E.P eller E[Ei] där E har kompileringstidstypen dynamic, är tilldelningen dynamiskt bunden (§12.3.3). I det här fallet är kompileringstidstypen för tilldelningsuttrycket dynamic, och lösningen som beskrivs nedan sker vid körning baserat på körningstypen för E. Om den vänstra operanden är av formuläret E[Ei] där minst ett element i Ei har kompileringstidstypen dynamicoch kompileringstidstypen för E inte är en matris, är den resulterande indexerarens åtkomst dynamiskt bunden, men med begränsad kompileringstidskontroll (§12.6.5).

En operation av formen x «op»= y bearbetas genom att tillämpa överbelastningsresolution för binära operatorer (§12.4.5) som om operationen skrevs x «op» y. Då

  • Om returtypen för den valda operatorn implicit kan konverteras till typen xutvärderas åtgärden som x = x «op» y, förutom att x utvärderas bara en gång.
  • Annars, om den valda operatorn är en fördefinierad operator, om returtypen för den valda operatorn uttryckligen är konvertibel till typen av x , och om y implicit kan konverteras 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 endast utvärderas 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 resultatet av eventuella komponentuttryck för x 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 B och C är metoder som returnerar int, anropas metoderna bara en gång, i den ordning A, B, C. slutexempel

När den vänstra operanden för en sammanlagd tilldelning är en egenskapsåtkomst eller indexeråtkomst, ska egenskapen eller indexeraren ha både en get-accessor och en set-accessor. Om så inte är fallet uppstår ett bindningstidsfel.

Med den andra regeln ovan kan x «op»= y 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 att x «op»= y tillåts om både x «op» y och 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 förhöjda operatorer. slutkommentar

12.21.5 Händelsetilldelning

Om den vänstra operand för a += or -= operatorn klassificeras som en händelseåtkomst utvärderas uttrycket enligt följande:

  • Instansuttrycket av åtkomst till händelsen, om det finns något, utvärderas.
  • Den högra operanden för +=- eller -=-operatorn 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 händelsetilldelningsuttryck 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 null literal).
  • Referenser till const-medlemmar i klass- och strukturtyper.
  • 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.
  • De fördefinierade unära operatorerna +, -, ! (logisk negation), och ~.
  • De fördefinierade +, -, *, /, %, <<, >>, &, |, ^, &&, ||, ==, !=, <, >, <=och >= binära operatorer.
  • Den villkorliga operatorn ?:.
  • Den ! null-förlåtande operatorn (§12.8.9).
  • sizeof uttryck, förutsatt att den ohanterade typen är en av de typer som anges i §23.6.9 som sizeof returnerar ett konstant värde för.
  • 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 värdet null.

Notering: Andra konverteringar, inklusive boxning, avboxning och implicita referenskonverteringar av icke-null-värden, är inte tillåtna 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 värde som inte ärnull 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 uppstår 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.

  • Deklarationer av konstanter (§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 uttalanden (§13.10.4)
  • Dimensionslängder i ett matrisskapandeuttryck (§12.8.17.5) som innehåller en initialiserare.
  • Attributen (§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 det konstanta uttrycket 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 sammanhang enligt 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. Den ?: 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 är implicit konverterbar till bool kommer den implicita konverteringen att tillämpas under körning.
  • Annars används enäringsöverbelastningslösning (§12.4.4) för att hitta den bästa möjliga unika implementeringen av operator trueE, och den implementeringen tillämpas vid programkörningstid.
  • Om ingen sådan operator hittas uppstår ett bindningstidsfel.