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 avthis
(§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
+
,-
,*
,/
ochnew
. 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 exempelx++
). - 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 metodenF
med det gamla värdet föri
. MetodenG
anropas med det gamla värdeti
och slutligen anropas metodenH
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 somx + (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 somx = (y = z)
. slutexempel
Prioritet och associativitet kan styras med parenteser.
Exempel:
x + y * z
multiplicerar försty
medz
och lägger sedan till resultatet ix
, men(x + y) * z
lägger först tillx
ochy
och multiplicerar sedan resultatet medz
. 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
ochfalse
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
, as
och 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
elleras
. 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ämpligtbool
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ärdenoperator «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 X
och y
är ett uttryck av typen Y
, bearbetas på följande sätt:
- Uppsättningen med användardefinierade operatorer som tillhandahålls av
X
ochY
för åtgärdenoperator «op»(x, y)
bestäms. Uppsättningen består av föreningen av de kandidatoperatörer som tillhandahålls avX
och de kandidatoperatörer som tillhandahålls avY
, 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
ochY
är identitetsvertibla, eller omX
ochY
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
ochY
och en operator«op»Y
som tillhandahålls avY
har samma returtyp som en«op»X
som tillhandahålls avX
samt operandtyperna av«op»Y
har en identitetskonvertering till motsvarande operandtyper av«op»X
, så sker endast«op»X
i uppsättningen.
- Om
- 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₀
. OmT
är en nullbar värdetyp ärT₀
dess underliggande typ. annars ärT₀
lika medT
. - För alla
operator «op»
deklarationer iT₀
och alla upplyfta former av sådana aktörer, om minst en operatör är tillämplig (§12.6.4.2) med avseende på argumentlistanA
, består uppsättningen av kandidatoperatörer av alla sådana tillämpliga operatörer iT₀
. - Om
T₀
ärobject
ä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örT₀
eller den effektiva basklassen förT₀
omT₀
ä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ärb
är enbyte
ochs
är enshort
, väljer överlagringsmatchningoperator *(int, int)
som den bästa operatorn. Effekten är därför attb
ochs
konverteras tillint
, och typen av resultat ärint
. När det gäller operationeni * d
, däri
är enint
ochd
är endouble
, väljeroverload
resolutionoperator *(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
, ushort
eller 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
decimal
konverteras den andra operanden till typendecimal
eller ett bindningstidsfel inträffar om den andra operanden är av typenfloat
ellerdouble
. - Om någon av operanderna är av typen
double
konverteras annars den andra operanden till typendouble
. - Om någon av operanderna är av typen
float
konverteras annars den andra operanden till typenfloat
. - Om annars någon av operanderna är av typen
ulong
, konverteras den andra operanden till typenulong
, eller ett bindningstidsfel uppstår om den andra operanden är avtype sbyte
,short
,int
ellerlong
. - Om någon av operanderna är av typen
long
konverteras annars den andra operanden till typenlong
. - Om antingen operand är av typen
uint
och den andra operanden är av typensbyte
,short
ellerint
konverteras båda operanderna till typenlong
. - Om någon av operanderna är av typen
uint
konverteras annars den andra operanden till typenuint
. - 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 typernadouble
ochfloat
. Regeln följer av det faktum att det inte finns några implicita konverteringar mellandecimal
-typen ochdouble
- ochfloat
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 avulong
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 endouble
. Felet löses genom att den andra operanden uttryckligen konverteras tilldecimal
, 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 ettnull
värde om operanden ärnull
. 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 ettnull
-värde om en eller båda operanderna ärnull
(ett undantag är&
- och|
-operatorerna avbool?
-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 ärbool
. 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 ettnull
-värde inte är lika med något icke-null
värde. Om båda operanderna inte ärnull
skriver den lyftande operatorn upp operanderna och tillämpar den underliggande operatorn för att skapabool
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 ärbool
. Det upplyfta formuläret skapas genom att en enda "?
"-modifierare läggs till varje operandtyp. Den upphöjda operatorn genererar värdetfalse
om en eller båda av operanderna ärnull
. Annars packar den lyftade operatorn upp operanderna och tillämpar den underliggande operatorn för att skapa ettbool
-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 namnetN
i var och en av de typer som anges som en primär begränsning eller sekundär begränsning (§15.2.5) förT
, tillsammans med uppsättningen tillgängliga medlemmar med namnetN
iobject
. - Annars består uppsättningen av alla tillgängliga (§7.5) medlemmar med namnet
N
iT
, inklusive ärvda medlemmar och de tillgängliga medlemmarna med namnetN
iobject
. OmT
är en konstruerad typ erhålls uppsättningen medlemmar genom att ersätta typargument enligt beskrivningen i §15.3.3. Medlemmar som har enoverride
-modifiering exkluderas från uppsättningen.
- Om
- Om
K
är noll tas sedan alla kapslade typer vars deklarationer innehåller typparametrar bort. OmK
inte är noll tas alla medlemmar med ett annat antal typparametrar bort. NärK
ä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ärS
är den typ där medlemmenM
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 avS
bort från uppsättningen. - Om
M
är en typdeklaration tas alla icke-typer som deklarerats i en bastyp avS
bort från uppsättningen och alla typdeklarationer med samma antal typparametrar somM
deklarerade i en bastyp avS
tas bort från uppsättningen. - Om
M
är en metod tas alla icke-metodmedlemmar som deklarerats i en bastyp avS
bort från uppsättningen.
- Om
- 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 ochT
har både en annan effektiv basklass änobject
och en icke-tom effektiv gränssnittsuppsättning (§15.2.5). För varje medlemS.M
i uppsättningen, därS
är den typ där medlemmenM
deklareras, tillämpas följande regler omS
är en annan klassdeklaration änobject
:- 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 somM
som deklareras i en gränssnittsdeklaration tas bort från uppsättningen.
- Om
- 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
ärobject
ellerdynamic
harT
ingen bastyp. - Om
T
är en enum_typeär bastyperna förT
klasstypernaSystem.Enum
,System.ValueType
ochobject
. - Om
T
är en struct_typeär bastyperna förT
klasstypernaSystem.ValueType
ochobject
.Anmärkning: En nullable_value_type är en struct_type (§8.3.1). slutkommentar
- Om
T
är en class_typeär bastyperna förT
basklasserna förT
, inklusive klasstypenobject
. - Om
T
är en interface_typeär bastyperna förT
basgränssnitten förT
och klasstypenobject
. - Om
T
är en array_typeär bastyperna förT
klasstypernaSystem.Array
ochobject
. - Om
T
är en delegate_typeär bastyperna förT
klasstypernaSystem.Delegate
ochobject
.
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
,y
ochvalue
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 ochP
ä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 ärstatic
är instansuttrycketthis
.T.F(x, y)
Överbelastningsupplösning används för att välja den bästa metoden F
i en klass eller en strukturT
. Ett bindningstidsfel uppstår om metoden inte ärstatic
. 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 typene
. Ett bindningstidsfel uppstår om metoden ärstatic
. Metoden anropas med instansuttryckete
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 omP
är skrivskyddad. OmP
inte ärstatic
är instansuttrycketthis
.P = value
Set-accessorn för egenskapen P
i den innehållande klassen eller struct anropas med argumentlistan(value)
. Ett kompileringsfel uppstår omP
är skrivskyddad. OmP
inte ärstatic
är instansuttrycketthis
.T.P
Hämtaren för egenskapen P
i klassen eller strukturenT
anropas. Ett kompileringsfel uppstår omP
inte ärstatic
eller omP
är skrivskyddad.T.P = value
Set-accessorn för egenskapen P
i klassen eller struct-T
anropas med argumentlistan(value)
. Ett kompileringsfel uppstår omP
inte ärstatic
eller omP
är skrivskyddad.e.P
Get-accessorn för egenskapen P
i klassen, strukturen eller gränssnittet som anges av typenE
anropas med instansuttryckete
. Ett bindningstidsfel uppstår omP
ärstatic
eller omP
är skrivskyddad.e.P = value
Set-accessorn för egenskapen P
i klassen, struct eller gränssnittet som anges av typen avE
anropas med instansuttryckete
och argumentlistan(value)
. Ett kompileringstidfel uppstår omP
ärstatic
eller omP
är skrivskyddat.Händelseåtkomst E += value
Tilläggsåtkomsten för händelsen E
i den innehållande klassen eller strukturen anropas. OmE
inte ärstatic
är instansuttrycketthis
.E -= value
Ta bort-accessorn för händelsen E
i den innehållande klassen eller structen anropas. OmE
inte ärstatic
är instansuttrycketthis
.T.E += value
Add-åtkomsten för händelsen E
i klassen eller strukturenT
anropas. Ett bindningstidsfel uppstår omE
inte ärstatic
.T.E -= value
Remove-accessorn för händelsen E
i klassen eller strukturenT
anropas. Ett bindningstidsfel uppstår omE
inte ärstatic
.e.E += value
Tilläggsåtkomsten för händelsen E
i klassen, strukturen eller gränssnittet som ges av typenE
anropas med instansuttryckete
. Ett bindningstidsfel uppstår omE
ärstatic
.e.E -= value
Ta bort-accessorn för händelsen E
i klassen, strukturen eller gränssnittet som anges av typenE
anropas med instansuttryckete
. Ett bindningstidsfel uppstår omE
ärstatic
.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 instansuttryckete
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 instansuttryckete
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
ochy
. 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 avM(c: false, valueB);
. Det första argumentet används utanför position (argumentet används i första positionen, men parametern med namnetc
ä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älvai
som ett indataargument, eftersom det klassificeras som en variabel och har samma typint
som indataparametern. IM1(i + 5)
-metodanropet skapas en namnlösint
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 enSystem.ArrayTypeMismatchException
kastas eftersom den faktiska elementtypen avb
ärstring
och inteobject
.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
ochstring
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 typU
och motsvarande parameter är en värdeparameter (§15.6.2.2) görs en lägre slutsatsdragning (§12.6.3.10) frånU
tillTᵢ
. - I annat fall om
Eᵢ
har en typU
och motsvarande parameter är en referensparameter (§15.6.2.3.3), eller utdataparameter (§15.6.2 3.4) görs en exakt slutsatsdragning (§12.6.3.9) frånU
tillTᵢ
. - I annat fall om
Eᵢ
har en typU
och motsvarande parameter är en indataparameter (§15.6.2.3.2) ochEᵢ
är ett indataargument, görs en exakt slutsatsdragning (§12.6.3.9) frånU
tillTᵢ
. - Om
Eᵢ
annars har en typU
och motsvarande parameter är en indataparameter (§15.6.2.3.2) görs en lägre bindningsslutsats (§12.6.3.10) frånU
tillTᵢ
. - 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) allaXₑ
ä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
- Det finns minst en typvariabel
- 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 parametertypTᵢ
där de utdatatyperna (§12.6.3.5) innehåller ofixerade typvariablerXₑ
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 T
indatatyper avE
med 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 avE
med 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 E
till en typ T på följande sätt:
- Om
E
är en anonym funktion med härledd returtypU
(§12.6.3.13) ochT
är en ombudstyp eller uttrycksträdtyp med returtypTₓ
, då görs en underordnad inferens (§12.6.3.10) frånU
tillTₓ
. - Om
E
är en metodgrupp ochT
är en ombudstyp eller uttrycksträdstyp med parametertyperT₁...Tᵥ
och returtypTₓ
, och överlagringslösning förE
med typernaT₁...Tᵥ
ger en enda metod med returtypU
, görs en lägre gränsinferens frånU
tillTₓ
. - Om
E
annars är ett uttryck med typenU
görs en lägre slutsatsdragningfrånU
tillT
. - Annars görs inga slutsatsdragningar.
12.6.3.8 Explicita parametertypinferenser
En explicit parametertypsinferens görs från ett uttryck E
för att en typ T
på följande sätt:
- Om
E
är en explicit typ av anonym funktion med parametertyperU₁...Uᵥ
ochT
är en delegattyp eller uttrycksträdstyp med parametertyperV₁...Vᵥ
görs för varjeUᵢ
en exakt slutsatsdragning (§12.6.3.9) frånUᵢ
till motsvarandeVᵢ
.
12.6.3.9 Exakta slutsatsdragningar
En exakt slutsatsdragningfrån en typ U
till en typ V
görs på följande sätt:
- Om
V
är en av de ofixeradeXᵢ
, läggsU
till i uppsättningen av exakta gränser förXᵢ
. - Annars bestäms uppsättningar
V₁...Vₑ
ochU₁...Uₑ
genom att kontrollera om något av följande fall gäller:-
V
är en matristypV₁[...]
ochU
är en matristypU₁[...]
av samma rangordning -
V
är typenV₁?
ochU
är typenU₁
-
V
är en konstruerad typC<V₁...Vₑ>
ochU
är en konstruerad typC<U₁...Uₑ>
Om något av dessa fall gäller görs en exakt slutsatsdragning från varjeUᵢ
till motsvarandeVᵢ
.
-
- Annars görs inga slutsatsdragningar.
12.6.3.10 Lägre bundna slutsatsdragningar
En lägre bunden slutsats från en typ U
till en typ V
görs på följande sätt:
- Om
V
är en av de ofixeradeXᵢ
, läggsU
till i uppsättningen av lägre gränser förXᵢ
. - Annars, om
V
är typenV₁?
ochU
är typenU₁?
, görs en lägre gränsinferens frånU₁
tillV₁
. - Annars bestäms uppsättningar
U₁...Uₑ
ochV₁...Vₑ
genom att kontrollera om något av följande fall gäller:-
V
är en matristypV₁[...]
ochU
är en matristypU₁[...]
av samma rangordning -
V
är en avIEnumerable<V₁>
,ICollection<V₁>
,IReadOnlyList<V₁>>
,IReadOnlyCollection<V₁>
ellerIList<V₁>
ochU
är en endimensionell matristypU₁[]
-
V
är en konstrueradclass
,struct
,interface
ellerdelegate
typC<V₁...Vₑ>
och det finns en unik typC<U₁...Uₑ>
så attU
(eller, omU
är en typparameter
, 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ånU
tillC<T>
eftersomU₁
kan varaX
ellerY
.)
Om något av dessa fall gäller görs en slutsats från varjeUᵢ
till motsvarandeVᵢ
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
ärC<V₁...Vₑ>
beror annars slutsatsdragningen på parameterni-th
typ avC
:- 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 U
till en typ V
görs på följande sätt:
- Om
V
är en av de ofixeradeXᵢ
läggsU
till i uppsättningen av övre gränser förXᵢ
. - Annars bestäms uppsättningar
V₁...Vₑ
ochU₁...Uₑ
genom att kontrollera om något av följande fall gäller:-
U
är en matristypU₁[...]
ochV
är en matristypV₁[...]
av samma rangordning -
U
är en avIEnumerable<Uₑ>
,ICollection<Uₑ>
,IReadOnlyList<Uₑ>
,IReadOnlyCollection<Uₑ>
ellerIList<Uₑ>
ochV
är en endimensionell matristypVₑ[]
-
U
är typenU1?
ochV
är typenV1?
-
U
är enC<U₁...Uₑ>
V
class, struct, interface
ellerdelegate
typ somidentical
till,inherits
från (direkt eller indirekt) eller implementerar (direkt eller indirekt) en unik typC<V₁...Vₑ>
- (Begränsningen "unikhet" innebär att med tanke på ett gränssnitt
C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}
görs ingen slutsatsdragning vid slutsatsdragning frånC<U₁>
tillV<Q>
. Slutsatsdragningar görs inte frånU₁
till antingenX<Q>
ellerY<Q>
.)
Om något av dessa fall gäller görs en slutsats från varjeUᵢ
till motsvarandeVᵢ
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
ärC<U₁...Uₑ>
beror annars slutsatsdragningen på parameterni-th
typ avC
:- 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 kandidattyper
Uₑ
börjar som alla typer inom gränserna förXᵢ
. - Varje bindning till
Xᵢ
granskas i sin tur: För varje exakt bunden U avXᵢ
tas alla typerUₑ
som inte är identiska medU
bort från kandidatuppsättningen. För varje lägre bundenU
avXᵢ
alla typerUₑ
som det finns inte en implicit konvertering frånU
tas bort från kandidatuppsättningen. För varje övre gräns U avXᵢ
alla typerUₑ
från vilka det finns inte en implicit konvertering tillU
tas bort från kandidatuppsättningen. - Om det bland de återstående kandidattyperna
Uₑ
finns en unik typV
som det finns en implicit konvertering till från alla andra kandidattyper, ärXᵢ
fast tillV
. - 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örF
samma som typen för det uttrycket. - Om kroppen av
F
är ett block och uppsättningen av uttrycken i instruktionerna i blocketreturn
har en bästa gemensamma typT
(§12.6.3.15), då är den härledda effektiva returtypen förF
T
. - 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 iF
antingen är ett uttryck som klassificeras som ingenting (§12.2), eller ett block där ingareturn
-uttryck har uttryck, är den härledda returtypen«TaskType»
(§15.15.1). - Om
F
är asynkron och har en härledd effektiv returtypT
är den härledda returtypen«TaskType»<T>»
(§15.15.1). - Om
F
inte är asynkron och har en härledd effektiv returtypT
är den härledda returtypenT
. - 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 klassenSystem.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 ettusing namespace
-direktiv och gav en klassCustomer
med enName
egenskap av typenstring
kan metodenSelect
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
varaCustomer
. Med hjälp av inferensprocessen för anonym funktionstyp som beskrivs ovan fårc
typCustomer
, och uttrycketc.Name
är relaterat till returtypen för väljareparametern, vilket härlederTResult
varastring
. Anropet är därmed likvärdigt medSequence.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 varastring
. Sedan får parametern för den första anonyma funktionen,s
, den härledda typenstring
, och uttrycketTimeSpan.Parse(s)
är relaterat till returtypen förf1
, och härlederY
att varaSystem.TimeSpan
. Slutligen får parametern för den andra anonyma funktionen,t
, den härledda typenSystem.TimeSpan
, och uttryckett.TotalHours
är relaterat till returtypen förf2
, och härlederZ
att varadouble
. Resultatet av anropet är därför av typendouble
.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 D
fö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 tillX
. -
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ᵥ)
medEᵢ
som argument och härledaX
. 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. OmA
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.
- parameteröverföringsläget för argumentet är identiskt med parameteröverföringsläget för motsvarande parameter och:
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.
- Om metodgruppen är resultatet av en simple_nameär en instansmetod endast tillämplig om
- 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ᵥ
tillQᵥ
inte bättre än den implicita konverteringen frånEᵥ
tillPᵥ
, och - för minst ett argument är konverteringen från
Eᵥ
tillPᵥ
bättre än konverteringen frånEᵥ
tillQᵥ
.
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 ochMₑ
är en allmän metod ärMᵢ
bättre änMₑ
. - Annars, om
Mᵢ
är tillämpligt i sin normala form ochMₑ
har en params-matris och endast är tillämplig i dess expanderade form, ärMᵢ
bättre änMₑ
. - 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örMₑ
, ärMᵢ
bättre änMₑ
. - Om
Mᵥ
annars har mer specifika parametertyper änMₓ
ärMᵥ
bättre änMₓ
. Låt{R1, R2, ..., Rn}
och{S1, S2, ..., Sn}
representera de oinstifierade och oexpandererade parametertyperna förMᵥ
ochMₓ
.Mᵥ
parametertyper är mer specifika änMₓ
omRx
för varje parameter inte är mindre specifik änSx
och för minst en parameter ärRx
mer specifik änSx
:- 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 iMₓ
ärMᵥ
bättre änMₓ
. - Om
Mᵥ
för minst en parameter använder bättre parameteröverföringsalternativ (§12.6.4.4) än motsvarande parameter iMₓ
och ingen av parametrarna iMₓ
använda det bättre parameteröverföringsalternativet änMᵥ
ärMᵥ
bättre änMₓ
. - 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 exaktT₁
ochE
matchar inte exaktT₂
(§12.6.4.6) -
E
exakt matchar både eller inget avT₁
ochT₂
, ochT₁
är ett bättre konverteringsmål änT₂
(§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 konverteringC₁
, ochT₂
inte är kompatibel med den enda bästa metoden från metodgruppen för konverteringC₂
12.6.4.6 Exakt matchande uttryck
Med ett uttryck E
och en typ T
matchar E
exaktT
om något av följande gäller:
-
E
har en typS
och det finns en identitetskonvertering frånS
tillT
-
E
är en anonym funktion ärT
antingen en ombudstypD
eller en uttrycksträdstypExpression<D>
och något av följande gäller:- Det finns en härledd returtyp
X
förE
i samband med parameterlistan överD
(§12.6.3.12), och det finns en identitetskonvertering frånX
till returtypen förD
-
E
är enasync
lambda utan returvärde ochD
har en returtyp som är en icke-generisk«TaskType»
- Antingen är
E
icke-asynkron ochD
har en returtypY
ellerE
är asynkron ochD
har en returtyp«TaskType»<Y>
(§15.15.1), och något av följande gäller:- Kroppen i
E
är ett uttryck som matchar exaktY
- Kroppen i
E
är ett block där varje retursats returnerar ett uttryck som exakt matcharY
- Kroppen i
- Det finns en härledd returtyp
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₁
tillT₂
och det finns ingen implicit konvertering frånT₂
tillT₁
-
T₁
är«TaskType»<S₁>
(§15.15.1),T₂
är«TaskType»<S₂>
ochS₁
är ett bättre konverteringsmål änS₂
-
T₁
är«TaskType»<S₁>
(§15.15.1),T₂
är«TaskType»<S₂>
ochT₁
är mer specialiserad änT₂
-
T₁
ärS₁
ellerS₁?
därS₁
är en signerad integrerad typ ochT₂
ärS₂
ellerS₂?
därS₂
är en osignerad integrerad typ. Specifikt:-
S₁
ärsbyte
ochS₂
ärbyte
,ushort
,uint
ellerulong
-
S₁
ärshort
ochS₂
ärushort
,uint
ellerulong
-
S₁
ärint
ochS₂
äruint
ellerulong
-
S₁
ärlong
ochS₂
ärulong
-
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ärdetypV
, ochM
deklareras eller åsidosätts iV
:-
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 klassificerasE
som en variabel. - Om
E
inte klassificeras som en variabel, eller omV
inte är en skrivskyddad strukturtyp (§16.2.2), ochE
ä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
E
och värdet förE
tilldelas till variabeln.E
omklassificeras sedan som en referens till den tillfälliga lokala variabeln. Den tillfälliga variabeln är tillgänglig somthis
inomM
, men inte på något annat sätt. Det är alltså bara närE
kan skrivas är det möjligt för anroparen att observera de ändringar somM
gör ithis
.- Argumentlistan utvärderas enligt beskrivningen i §12.6.2.
-
M
anropas. Variabeln som refereras avE
blir variabeln som refereras avthis
.
-
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 konverteraE
till en class_type, ochE
anses vara av den class_type i följande steg. Om value_type är en enum_type, är class_typeSystem.Enum;
, annars är detSystem.ValueType
. - Värdet för
E
kontrolleras för att vara giltigt. Om värdet förE
är null genereras enSystem.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 avM
som tillhandahålls av instansens körningstidstyp som refereras avE
. Den här funktionsmedlemmen bestäms genom att tillämpa reglerna för gränssnittsmappning (§18.6.5) för att fastställa implementeringen avM
som tillhandahålls av körningstiden för den instans som refereras avE
. - Annars om
M
är en virtuell funktion, ska funktionsmedlemmen som ska anropas vara implementeringen avM
som tillhandahålls av körningstidenstypen för instansen som refereras avE
. Denna funktionsmedlem bestäms genom tillämpning av reglerna för att fastställa den mest härledda implementeringen (§15.6.4) avM
med avseende på körtidstypen för den instans som refereras avE
. - Annars är
M
en icke-virtuell funktionsmedlem och funktionsmedlemmen som ska anropas ärM
sig själv.
- Om bindningstidstypen för
- 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
ellerSystem.Enum
. slutkommentar - 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 E
dekonstrueras till ett tuppeluttryck med n
element på följande sätt:
- Om
E
är ett tupeluttryck medn
element, är resultatet av dekonstruktion uttrycketE
. - Annars, om
E
har tuppeltypen(T1, ..., Tn)
medn
element, då utvärderasE
till en temporär variabel__v
och 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
ochpointer_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) ochSystem.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) ochSystem.FormattableString
(§C.3). Beskrivningar av standardformat, som är identiska för Regular_Interpolation_Format och Verbatim_Interpolation_Format, finns i dokumentationen förSystem.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 talI
från0
tillN-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 (
}
)
- Ett vänster klammerparentes (
- Tecknen i Interpolated_Regular_String_Mid eller Interpolated_Verbatim_String_Mid som omedelbart följer efter den motsvarande interpolationen, om någon
- En platshållarspecifikation:
- 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 namnI
, 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 namnetI
, 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 avT
innehåller en typparameter med namnetI
refererar simple_name till den typparametern. - Annars, om en medlemssökning (§12.5) av
I
iT
med argument ave
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 avthis
. 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 formenthis.I
. Detta kan bara inträffa näre
är noll. - Annars är resultatet detsamma som en medlemsåtkomst (§12.8.7) i form av
T.I
ellerT.I<A₁, ..., Aₑ>
.
- Om
- Om
- 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 ochI
är namnet på ett namnområde iN
, 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 namnetI
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
iN
.
- Om platsen där simple_name inträffar omges av en namnområdesdeklaration för
- Annars, om
N
innehåller en tillgänglig typ med namnI
oche
typparametrar, så:- Om
e
är noll och platsen där simple_name inträffar omges av en namnområdesdeklaration förN
och namnområdesdeklarationen innehåller en extern_alias_directive eller using_alias_directive som associerar namnetI
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.
- Om
- 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 namnetI
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
oche
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
oche
typparametrar, så är simple_name tvetydig och ett kompileringsfel inträffar.
- Om
Obs: Hela det här steget är exakt parallellt med motsvarande steg i bearbetningen av en namespace_or_type_name (§7.8). slutkommentar
- Om
- Annars, om
e
är noll ochI
ä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
Ni
ska tuppelns typelement varaTi Ni
. - Om
Ei
är i formenNi
ellerE.Ni
ellerE?.Ni
ska tupelelementet varaTi 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
ellerE.Ni
ellerE?.Ni
eller -
Ni
är av formenItemX
, därX
är en sekvens av icke-0
-initierade decimalsiffror som kan representera positionen för ett tupelelement, ochX
inte representerar elementets position.
- Ett annat element i tuppelns uttryck har namnet
- 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
ocht2
, använder inte typen av tuppelns uttryck, utan tillämpar i stället en implicit tuppelkonvertering. När det gällert2
förlitar sig den implicita tupleomvandlingen på de implicita konverteringarna från2
tilllong
och frånnull
tillstring
. Det tredje tuppelns uttryck har en typ(int i, string)
och kan därför omklassificeras som ett värde av den typen. Deklarationen avt4
, å 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 ochE
är ett namnområde ochE
innehåller ett kapslat namnområde med namnetI
blir resultatet det namnområdet. - Annars, om
E
är ett namnområde ochE
innehåller en tillgänglig typ med namnI
ochK
typparametrar, blir resultatet den typen som skapas med de angivna typargumenten. - Om
E
klassificeras som en typ, omE
inte är en typparameter, och om en medlemssökning (§12.5) avI
iE
medK
typparametrar genererar en matchning, utvärderasE.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
iE
. - Annars är resultatet en variabel, nämligen det statiska fältet
I
iE
.
- 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
- 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 omI
var ett statiskt fält. - Annars är resultatet en händelseåtkomst utan associerat instansuttryck.
- 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
- 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
- Om
E
är en egenskapsåtkomst, indexerareåtkomst, variabel eller värde, vars typ ärT
och en medlemssökning (§12.5) avI
iT
med argument avK
typ skapar en matchning, utvärderasE.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 avE
. - Om
I
identifierar en instansegenskap blir resultatet en egenskapsåtkomst med ett associerat instansuttryck avE
och en associerad typ som är egenskapens typ. OmT
ä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 medT
och söker igenom dess basklasser. - Om
T
är en class_type ochI
identifierar ett instansfält för den class_type:- Om värdet för
E
ärnull
genereras enSystem.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 avE
. - Annars är resultatet en variabel, nämligen fältet
I
i objektet som refereras avE
.
- Om värdet för
- Om
T
är en struct_type ochI
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ältetI
i den struct-instans som anges avE
. - Annars är resultatet en variabel, nämligen fältet
I
i struct-instansen som anges avE
.
- Om
- 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, bearbetasE.I
exakt som omI
var ett instansfält. - Annars är resultatet en händelseåtkomst med ett associerat instansuttryck av
E
.
- 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
- Först erhålls värdet för egenskaps- eller indexeråtkomst om
- I annat fall görs ett försök att bearbeta
E.I
som ett tilläggsmetodanrop (§12.8.10.3). Om detta misslyckas ärE.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 denColor
-identifierare som refererar tillColor
-typ med«...»
, och de som refererar till fältetColor
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 avP.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 avE
T?
, och innebörden avE
ä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
E
T
och innebörden avE
ä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 uttryckP.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 avE
T?
, och innebörden avE
ä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
E
T
och innebörden avE
ä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 tillnull
utvärderas varkenA₀
ellerA₁
. 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
returnerartrue
kanp
på ett säkert sätt avrefereras för att få åtkomst till egenskapenName
, 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 omx
ärnull
vid körning, kommer ett undantag att genereras eftersomnull
inte kan omvandlas tillint
.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
Assign
parametrar,lv
&rv
, ärstring?
, därlv
är en utdataparameter och utför en enkel tilldelning.Metod
M
skickar variabelns
, av typenstring
, somAssign
:s utdataparameter. Kompilatorn ger en varning eftersoms
inte är en nullbar variabel. Eftersom det andra argumentet iAssign
inte 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 dynamic
genomgå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 medT
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 medT
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 metodgruppenM
:- Om
F
inte är generisk ärF
en kandidat när:-
M
har ingen typargumentlista och -
F
gäller förA
(§12.6.4.2).
-
- Om
F
är generisk ochM
inte har någon typargumentlista ärF
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 överF
gäller förA
(§12.6.4.2)
- Om
F
är generisk ochM
innehåller en typargumentlista ärF
en kandidat när:
- Om
- 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ärC
är den typ där metodenF
deklareras, tas alla metoder som deklarerats i en bastyp avC
bort från uppsättningen. OmC
är en annan klasstyp änobject
tas 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äggsmetoderMₑ
ä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äggsmetoderMₑ
, är uppsättningen med dessa tilläggsmetoder kandidatuppsättningen.
- Om det angivna namnområdet eller kompileringsenheten direkt innehåller icke-generiska typdeklarationer
- 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 ochC
metoden 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 överC.G
ochE.F
har företräde framför bådeD.F
ochC.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örD
ärnull
genereras enSystem.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 dynamic
genomgå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
, long
eller 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 typenshort
utförs en implicit konvertering tillint
, eftersom implicita konverteringar frånshort
tillint
och frånshort
tilllong
ä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örP
ärnull
genereras enSystem.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 ettSystem.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 iT
eller en bastyp avT
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ärS
är den typ där indexerarenI
deklareras:- Om
I
inte gäller förA
(§12.6.4.2), tasI
bort från uppsättningen. - Om
I
gäller förA
(§12.6.4.2), tas alla indexerare som deklarerats i en bastyp avS
bort från uppsättningen. - Om
I
gäller förA
(§12.6.4.2) ochS
är en annan klasstyp änobject
tas alla indexerare som deklarerats i ett gränssnitt bort från uppsättningen.
- Om
- 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 medA
och en associerad typ som är indexerarens typ. OmT
ä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 medT
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 uttryckP.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 avE
T?
, och innebörden avE
ä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
E
T
och innebörden avE
ä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 uttryckP[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 avE
T?
, och innebörden avE
ä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
E
T
och innebörden avE
ä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 tillnull
utvärderas varkenA₀
ellerA₁
. 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 enref
parameter av struct-typen. I synnerhet innebär det att variabeln anses vara ursprungligen tilldelad.
- Om konstruktordeklarationen inte har någon konstruktorinitierare fungerar variabeln
- 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 struct
fungerar variabelnthis
exakt samma som en indataparameter av structtypen - Annars beter sig variabeln
this
exakt på samma sätt som enref
parameter av struct-typen
- Om structen är en
- 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.
- Om metoden eller accessorn inte är en iterator (§15.14) eller asynkron funktion (§15.15) representerar variabeln
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
ärbase
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
, decimal
och 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 avx
. - 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 ärstatic
) och argumentlistan (omx
är en indexeringsåtkomst) som är associerad medx
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örx
anropas med det här värdet som värdeargument. - Det sparade värdet för
x
blir resultatet av åtgärden.
- Instansuttrycket (om
Operatörerna ++
och --
stöder även prefix notation (§12.9.6). 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.
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 ochA
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örT
enligt definitionen i §8.3.3.
-
object_creation_expression är en standardkonstruktoranrop. Resultatet av object_creation_expression är ett värde av typen
- Annars, om
T
är en type_parameter ochA
inte finns:- Om ingen begränsning av värdetyp eller konstruktor (§15.2.5) har angetts för
T
uppstå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.
- Om ingen begränsning av värdetyp eller konstruktor (§15.2.5) har angetts för
- 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örA
(§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.
- Om
Ä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 enSystem.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.
- En ny instans av klass
- 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.
- En instans av typen
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
Rectangle
konstruktor allokerar de två inbäddadePoint
-instanserna kan de användas för att initiera inbäddadePoint
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 typenint[,]
, och matrisens skapandeuttryck nyint[10][,]
skapar en matrisinstans av typenint[][,]
. 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 ärnull
. Det är inte möjligt för samma matriscreeringsuttryck att också instansiera undermatriserna, och påståendetint[][] 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
ellerstring
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 varaobject[]
. 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ånE
tillD
.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ånE
tillD
.Om
E
är ett värde skaE
vara kompatibelt (§20.2) medD
, och resultatet är en referens till en nyligen skapad delegering med en anropslista med en enda post som anroparE
.
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ånE
tillD
. - Om
E
är en anonym funktion utvärderas skapandet av ombudet som en anonym funktionskonvertering frånE
tillD
(§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
ärnull
genereras enSystem.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 enSystem.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 andraSquare
-metoden, eftersom metoden matchar exakt parameterlistan och returtypen avDoubleFunc
. Om den andraSquare
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
ochp2
är av samma anonyma typ.slutexempel
Metoderna Equals
och GetHashcode
för anonyma typer åsidosätter de metoder som ärvts från object
och 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, inklusivevoid
-metoder, med en instans avSystem.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
ochSystem.Int32
är av samma typ. Resultatet avtypeof(X<>)
beror inte på typargumentet, men resultatet avtypeof(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
ellerdouble
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 enSystem.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
enSystem.OverflowException
, och metodenG
returnerar –727379968 (de lägre 32 bitarna i resultatet som inte ligger inom intervallet). Beteendet för metodenH
beror på standardkontexten för spillkontroll för kompilering, men den är antingen samma somF
eller samma somG
.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
ochH
orsakar kompileringsfel som rapporteras eftersom uttrycken utvärderas i enchecked
-kontext. Ett överflöde uppstår också när det konstanta uttrycket utvärderas iG
, men eftersom utvärderingen sker i ettunchecked
-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 avx * y
iMultiply
, 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örint
intervallet, utan operatornunchecked
, skulle gjutningarna tillint
generera kompileringsfel.slutexempel
Obs: Operatorer och instruktioner för
checked
ochunchecked
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
span8
resulterarstackalloc
i enSpan<int>
, som konverteras av en implicit operator tillReadOnlySpan<int>
. På samma sätt konverteras den resulterandespan9
förSpan<double>
till den användardefinierade typenWidget<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 string
och 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 typList<T>
deklareras inomSystem.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 ochnameof(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örX
är det minsta representable-värdet av operandetypen (−2³¹ förint
eller −2⁶³ förlong
), kan den matematiska negationen avX
inte representeras inom operandtypen. Om detta inträffar inom enchecked
-kontekst kastas enSystem.OverflowException
; om det inträffar inom enunchecked
-kontekst är resultatet operandens värde och överflödet rapporteras inte.Om negationsoperatorns operand är av typen
uint
konverteras den till typenlong
och resultatets typ ärlong
. Ett undantag är regeln som tillåter attint
-värdet−2147483648
(−2³¹) skrivs som decimal heltal (§6.4.5.3).Om negationsoperatorns operand är av typen
ulong
uppstår ett kompileringsfel. Ett undantag är regeln som tillåter attlong
-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. Omx
ärNaN
blir 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 typenSystem.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 true
blir resultatet false
. Om operand är false
blir 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
, decimal
och 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 avx
och blir resultatet av åtgärden.
-
- Om
x
klassificeras som åtkomst till en egenskap eller indexerare:- Instansuttrycket (om
x
inte ärstatic
) och argumentlistan (omx
är en indexeringsåtkomst) som är associerad medx
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örx
anropas med det här värdet som värdeargument. - Det här värdet blir också resultatet av åtgärden.
- Instansuttrycket (om
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 T
inträ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 skrivax
) eller som en additive_expression kombinerad med en parenthesized_expression (som beräknar värdetx – 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) utomas
ochis
.
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
ochy
är identifierare ärx.y
korrekt grammatik för en typ, även omx.y
faktiskt inte anger någon typ. slutexempel
Obs: Från disambiguationsregeln följer det att om
x
ochy
är identifierare(x)y
,(x)(y)
och(x)(-y)
är cast_expressions, men(x)-y
är det inte, även omx
identifierar en typ. Men omx
är ett nyckelord som identifierar en fördefinierad typ (till exempelint
) ä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 kompileringstidstypdynamic
-
t
har en tillgänglig instans eller tilläggsmetod som heterGetAwaiter
utan parametrar och inga typparametrar, och en returtypA
för vilken alla följande villkor gäller:-
A
implementerar gränssnittetSystem.Runtime.CompilerServices.INotifyCompletion
(kallas därefterINotifyCompletion
för korthet) -
A
har en tillgänglig, läsbar instansegenskapIsCompleted
av typenbool
-
A
har en tillgänglig instansmetodGetResult
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 void
klassificeras await_expression som ingenting. Om den har en icke-void
returtyp T
klassificeras 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
bool
b
erhålls genom att utvärdera uttrycket(a).IsCompleted
. - Om
b
ärfalse
beror utvärderingen på oma
implementerar gränssnittetSystem.Runtime.CompilerServices.ICriticalNotifyCompletion
(kallas därefterICriticalNotifyCompletion
för korthet). Den här kontrollen görs vid bindningstillfället. d.v.s. vid körning oma
har kompileringstidstypendynamic
och vid kompileringstillfället annars. Låtr
beteckna återupptagandedelegaten (§15.15):- Om
a
inte implementerarICriticalNotifyCompletion
utvärderas uttrycket((a) as INotifyCompletion).OnCompleted(r)
. - Om
a
implementerarICriticalNotifyCompletion
utvärderas uttrycket((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r)
. - Utvärderingen pausas sedan och kontrollen returneras till den aktuella anroparen för funktionen async.
- Om
- Antingen omedelbart efter (om
b
ärtrue
), eller vid senare anrop av återupptagningsdelegaten (omb
ärfalse
), 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 * y
tillä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 enSystem.OverflowException
om produkten ligger utanför resultattypens intervall. I ettunchecked
-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
ochy
positiva ändliga värden.z
är resultatet avx * y
, avrundat till närmaste värde som kan representeras. Om resultatet är för stort för måltypen ärz
oändlighet. På grund av avrundning kanz
vara noll trots att varkenx
ellery
ä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 typenSystem.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 / y
tillä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
ellerlong
värde och den högra operanden är–1
, uppstår en översvämning. I ettchecked
-kontekst leder detta till att enSystem.ArithmeticException
(eller en underklass därav) kastas. I ettunchecked
-sammanhang är det implementeringsdefinierat om enSystem.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
ochy
positiva ändliga värden.z
är resultatet avx / 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 enSystem.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örx
mindre skalan föry
.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 % y
tillä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 avx – (x / y) * y
. Omy
är noll utlöses enSystem.DivideByZeroException
.Om den vänstra operanden är det minsta
int
- ellerlong
-värdet och den högra operanden är–1
genereras enSystem.OverflowException
om och bara omx / 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
ochy
positiva ändliga värden.z
är resultatet avx % y
och beräknas somx – n * y
, där n är det största möjliga heltalet som är mindre än eller lika medx / 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ärn
är det heltal som är närmastx / 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 enSystem.ArithmeticException
(eller en underklass därav) genereras. Ett genomförande i enlighet med detta får inte utlösa ett undantag förx % y
i alla fall därx / 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örx
.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 + y
tillä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 string
sammanfogar 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 enSystem.OverflowException
. I ettunchecked
-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
ochy
icke-nollfinita värden ochz
är resultatet avx + y
. Omx
ochy
har samma storlek men motsatta tecken ärz
positiv noll. Omx + y
är för stor för att representeras i måltypen ärz
en oändlighet med samma tecken somx + 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 ochU
är den underliggande typen avE
: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 ärnull
, ersätts den med en tom sträng. Annars konverteras alla icke-string
operander till dess strängrepresentation genom att anropa den virtuellaToString
metod som ärvs från typenobject
. OmToString
returnerarnull
ersä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 ettnull
värde. EnSystem.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å ärnull
). Om den andra operanden annars ärnull
ä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 – y
tillä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 enSystem.OverflowException
. I ettunchecked
-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
ochy
icke-nollfinita värden ochz
är resultatet avx – y
. Omx
ochy
är lika ärz
positiv noll. Omx – y
är för stor för att representeras i måltypen ärz
en oändlighet med samma tecken somx – 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 avy
, 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 ochU
är den underliggande typen avE
: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örx
ochy
, 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
null
blir resultatet av åtgärdennull
. - 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.
- Om listorna är lika, enligt vad som bestäms av delegeringens likhetsoperator (§12.12.9), är resultatet av operationen
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
- Om den första operanden är
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 >> count
tillä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
<<
skiftarx
å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
>>
skiftarx
rätt med ett antal bitar som beräknas enligt beskrivningen nedan.När
x
är av typenint
ellerlong
ignoreras de låga bitarna ix
, de återstående bitarna flyttas åt höger och de tomma bitpositionerna i hög ordning är inställda på noll omx
är icke-negativ och anges till en omx
är negativ.När
x
är av typenuint
ellerulong
ignoreras de lågordningsbitar avx
, 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
ärint
elleruint
bestäms skiftantalet av de fem lägst ordnade bitarna icount
. Med andra ord beräknas skiftantalet fråncount & 0x1F
. - När typen av
x
ärlong
ellerulong
anges skiftantalet av de sex lägsta bitarna avcount
. Med andra ord beräknas skiftantalet fråncount & 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 typenint
, utför åtgärdenunchecked ((int)((uint)x >> y))
en logisk högerförskjutning förx
. 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 ==
, !=
, <
, >
, <=
, >=
, is
och 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 y false annars |
x != y |
true om x inte är lika med y false annars |
x < y |
true om x är mindre än y false annars |
x > y |
true om x är större än y false annars |
x <= y |
true om x är mindre än eller lika med y false annars |
x >= y |
true om x är större än eller lika med y false annars |
12.12.2 Jämförelseoperatorer för heltal
De fördefinierade heltalsjämförelseoperatorerna är:
bool operator ==(int x, int y);
bool operator ==(uint x, uint y);
bool operator ==(long x, long y);
bool operator ==(ulong x, ulong y);
bool operator !=(int x, int y);
bool operator !=(uint x, uint y);
bool operator !=(long x, long y);
bool operator !=(ulong x, ulong y);
bool operator <(int x, int y);
bool operator <(uint x, uint y);
bool operator <(long x, long y);
bool operator <(ulong x, ulong y);
bool operator >(int x, int y);
bool operator >(uint x, uint y);
bool operator >(long x, long y);
bool operator >(ulong x, ulong y);
bool operator <=(int x, int y);
bool operator <=(uint x, uint y);
bool operator <=(long x, long y);
bool operator <=(ulong x, ulong y);
bool operator >=(int x, int y);
bool operator >=(uint x, uint y);
bool operator >=(long x, long y);
bool operator >=(ulong x, ulong y);
Var och en av dessa operatorer jämför de numeriska värdena för de två heltalsopernderna och returnerar ett bool
värde som anger om den specifika relationen är true
eller false
.
Lyfts (§12.4.8) 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
ochy
är NaN ärx < y
false
, men!(x >= y)
ärtrue
. 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 bool
genererar 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 typenT
därT
ä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 operandensHasValue
egenskap enligt beskrivningen i (§12.12.10). - Om
T
vid körning är en referenstyp blir resultatettrue
om operanden ärnull
ochfalse
annars.
- Om
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
ellerx != y
, om det finns någon tillämplig användardefinieradoperator ==
elleroperator !=
, 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 typenobject
.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 omT
kan representera en värdetyp som inte kan nulleras, och resultatet definieras helt enkelt somfalse
närT
ä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
ocht
refererar till två distinkta stränginstanser som innehåller samma tecken. Den första jämförelsen gerTrue
eftersom den fördefinierade strängjämförelseoperatorn (§12.12.8) väljs när båda operanderna är av typenstring
. Alla återstående jämförelser ger utdataFalse
eftersom överbelastningen av typenoperator ==
istring
inte är tillämplig om någon av operanderna har en bindningstidstyp avobject
.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 inneslutnaint
-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 ärnull
. - 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.HasValue
och de två sista formulären översätts till x.HasValue
.
12.12.11 Tuppelns likhetsoperatorer
Tuppelns likhetsoperatorer tillämpas parvis på elementen i tuppelns operander i lexikal ordning.
Om varje 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 yi
ska samma likhetsoperator tillämpas och ge ett resultat av typen bool
, dynamic
, en typ som har en implicit konvertering till bool
eller 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
ochyi
i lexikal ordning:- Operatorn
xi == yi
utvärderas och ett resultat av typenbool
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 operatornfalse
dynamiskt på den, och det resulterandebool
värdet negeras med operatorn för logisk negation (!
). - Om jämförelsetypen annars har en implicit konvertering till
bool
tillämpas konverteringen. - Om jämförelsetypen annars har en operator
false
anropas operatorn och det resulterandebool
värdet negeras med operatorn för logisk negation (!
).
- Om jämförelsen gav ett
- Om den resulterande
bool
ärfalse
sker ingen ytterligare utvärdering och resultatet av likhetsoperatorn för tupler ärfalse
.
- Operatorn
- Om alla elementjämförelser gav
true
, blir resultatet av tuplens likhetsoperatortrue
.
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
ochyi
i lexikal ordning:- Operatorn
xi != yi
utvärderas och ett resultat av typenbool
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 operatorntrue
dynamiskt på den, och det resulterandebool
värdet är resultatet. - Om jämförelsetypen annars har en implicit konvertering till
bool
tillämpas konverteringen. - Om jämförelsetypen annars har en operator
true
anropas operatorn och det resulterandebool
värdet är resultatet.
- Om jämförelsen gav ett
- Om den resulterande
bool
ärtrue
sker ingen ytterligare utvärdering och resultatet av likhetsoperatorn för tupler ärtrue
.
- Operatorn
- Om alla elementjämförelser gav
false
, blir resultatet av tuplens likhetsoperatorfalse
.
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:
- Om
E
är en anonym funktion eller metodgrupp uppstår ett kompileringsfel. - Om
E
ärnull
literal, eller om värdet förE
ärnull
, blir resultatetfalse
. - Annars:
- Låt
R
vara körningstyp förE
. - Låt
D
härledas frånR
på följande sätt: - Om
R
är en nullbar värdetyp ärD
den underliggande typen avR
. - Annars är
D
R
. - Resultatet beror på
D
ochT
på följande sätt: - Om
T
är en referenstyp, är resultatettrue
om så är fallet:- det finns en identitetskonvertering mellan
D
ochT
, -
D
är en referenstyp och en implicit referenskonvertering frånD
tillT
finns, eller - Antingen:
D
är en värdetyp och det finns en boxningskonvertering frånD
tillT
.
Eller:D
är en värdetyp ochT
är en gränssnittstyp som implementeras avD
.
- det finns en identitetskonvertering mellan
- Om
T
är en nullbar värdetyp, är resultatettrue
omD
är den underliggande typen avT
. - Om
T
är en värdetyp som inte kan nullerastrue
resultatet omD
ochT
är av samma typ. - 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ärC
är kompileringstidstypen förE
:
- Om kompileringstidstypen för
e
är samma somT
, 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 avE
tillT
:
- Om
C
är av en värdetyp som inte kan vara null, är resultatet av åtgärdentrue
.- 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
tillT
, eller omC
ellerT
ä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 typenT
möjlig, och resultatet av åtgärden ärfalse
. 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å typenT
.
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 T
ska 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
ochT
. - Typen av
E
ellerT
är en öppen typ. -
E
ärnull
bokstavligt talat.
Om kompileringstidstypen för E
inte är dynamic
genererar åtgärden E as T
samma resultat som
E is T ? (T)(E) : (T)null
förutom att E
endast utvärderas en gång. 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
avG
är känd för att vara en referenstyp, eftersom den har klassbegränsningen. TypparameternU
avH
är dock inte; därför tillåts inte användningen avas
-operatorn iH
.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 U
och «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 false
eller x
är false
och y
är true
. Annars blir resultatet false
. När operanderna är av typen bool
berä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
, false
och 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ärdenx & y
, förutom atty
endast utvärderas omx
inte ärfalse
. - Åtgärden
x || y
motsvarar åtgärdenx | y
, förutom atty
endast utvärderas omx
inte ärtrue
.
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
returnerarfalse
ochoperator false
returnerarfalse
. 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 somx ? y : false
. Med andra ord utvärderasx
först och konverteras till typenbool
. Omx
sedan ärtrue
utvärderasy
och konverteras till typenbool
och detta blir resultatet av åtgärden. Annars är resultatet av åtgärdenfalse
. - Åtgärden
x || y
utvärderas somx ? true : y
. Med andra ord utvärderasx
först och konverteras till typenbool
. Omx
sedan ärtrue
blir resultatet av åtgärdentrue
. Annars utvärderas och konverterasy
till typenbool
och 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 typenT
och returnera ett resultat av typenT
. -
T
skall innehålla deklarationer avoperator true
ochoperator 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 somT.false(x) ? x : T.&(x, y)
, därT.false(x)
är ett anrop avoperator false
som deklareras iT
ochT.&(x, y)
är ett anrop av den valdaoperator &
. Med andra ord utvärderasx
först ochoperator false
anropas på resultatet för att avgöra omx
är definitivt falskt. Omx
är definitivt falskt är resultatet av åtgärden det värde som tidigare beräknats förx
. Annars utvärderasy
och den valdaoperator &
anropas på värdet som tidigare beräknats förx
och värdet som beräknas föry
för att generera resultatet av åtgärden. - Åtgärden
x || y
utvärderas somT.true(x) ? x : T.|(x, y)
, därT.true(x)
är ett anrop avoperator true
som deklareras iT
ochT.|(x, y)
är ett anrop av den valdaoperator |
. Med andra ord utvärderasx
först ochoperator true
anropas på resultatet för att avgöra omx
är definitivt sant. Omx
är definitivt sant är resultatet av åtgärden det värde som tidigare beräknats förx
. Annars utvärderasy
och den valdaoperator |
anropas på värdet som tidigare beräknats förx
och värdet som beräknas föry
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 soma ?? (b ?? c)
. I allmänna termer returnerar ett uttryck för formuläretE1 ?? E2 ?? ... ?? EN
den första operander som inte ärnull
, ellernull
om alla operander ärnull
. slutexempel
Vilken typ av uttryck a ?? b
beror på vilka implicita konverteringar som är tillgängliga på operanderna. I prioritetsordning är typen av a ?? b
A₀
, 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 ochb
är ett dynamiskt uttryck är resultattypen annarsdynamic
. Vid körning utvärderasa
först. Oma
inte ärnull
konverterasa
tilldynamic
och detta blir resultatet. Annars utvärderasb
och detta blir resultatet. - Annars, om
A
finns och är en nullbar värdetyp och det finns en implicit konvertering frånb
tillA₀
, är resultattypenA₀
. Vid körning utvärderasa
först. Oma
inte ärnull
behandlasa
som typA₀
, och detta blir resultatet. Annars utvärderasb
och konverteras till typenA₀
, och detta blir resultatet. - Annars är resultattypen
A
omb
finns och det finns en implicit konvertering frånA
tillA
. Vid körning utvärderasa
först. Oma
inte ärnull
blira
resultatet. Annars utvärderasb
och konverteras till typenA
, och detta blir resultatet. - Annars, om
A
finns och är en nullbar värdetyp,b
har en typB
och det finns en implicit konvertering frånA₀
tillB
, är resultattypenB
. Vid körning utvärderasa
först. Oma
inte ärnull
ocha
packas upp till typA₀
och konverteras till typB
, vilket blir resultatet. Annars utvärderasb
och blir resultatet. - Annars, om
b
har en typB
och det finns en implicit konvertering fråna
tillB
, är resultattypenB
. Vid körning utvärderasa
först. Oma
inte ärnull
konverterasa
till typB
och detta blir resultatet. Annars utvärderasb
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.Exception
och 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
out
argument_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 avb1
ärbool
eftersom det är typen av motsvarande utdataparameter iM1
. EfterföljandeWriteLine
kan komma åti1
ochb1
, som har införts i den omgivande omfattningen.Deklarationen av
s2
visar ett försök att användai2
i det kapslade anropet tillM
, vilket inte är tillåtet, eftersom referensen inträffar i argumentlistan däri2
deklarerades. Å andra sidan tillåts referensen tillb2
i det slutliga argumentet, eftersom den inträffar efter slutet av listan över kapslade argument därb2
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
_
motsvararvar _
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 true
utvä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 soma ? b : (c ? d : e)
. slutexempel
Den första operanden för ?:
-operatorn ska vara ett uttryck som implicit kan konverteras till bool
eller ett uttryck av en typ som implementerar operator true
. Om inget av dessa krav uppfylls uppstår ett kompileringsfel.
Om ref
finns:
- Det ska finnas en identitetskonvertering mellan typerna av de två variable_referenceoch resultatets typ kan vara av båda typerna. Om någon av typerna är
dynamic
föredrar typinferensdynamic
(§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 typX
ochy
har typY
- Om det finns en identitetskonvertering mellan
X
ochY
är resultatet den bästa gemensamma typen av en mängd uttryck (§12.6.3.15). Om någon av typerna ärdynamic
föredrar typinferensdynamic
(§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
tillY
, men inte frånY
tillX
, ärY
typen av villkorsuttryck. - Annars, om det finns en implicit uppräkningskonvertering (§10.2.4) från
X
tillY
, ärY
typen av villkorsuttryck. - Annars, om det finns en implicit uppräkningskonvertering (§10.2.4) från
Y
tillX
, ärX
typen av villkorsuttryck. - Om det annars finns en implicit konvertering (§10.2) från
Y
tillX
, men inte frånX
tillY
, ärX
typen av villkorsuttryck. - Annars kan ingen uttryckstyp fastställas och ett kompileringsfel inträffar.
- Om det finns en identitetskonvertering mellan
- Om bara en av
x
ochy
har en typ, och bådex
ochy
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ämsbool
-värdet förb
.- Om det finns en implicit konvertering från typen av
b
tillbool
utförs den implicita konverteringen för att skapa ettbool
värde. - Annars anropas
operator true
som definieras av typenb
för att skapa ettbool
värde.
- Om det finns en implicit konvertering från typen av
- Om det
bool
värde som skapas av steget ovan ärtrue
utvärderasx
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ämsbool
-värdet förb
.- Om det finns en implicit konvertering från typen av
b
tillbool
utförs den implicita konverteringen för att skapa ettbool
värde. - Annars anropas
operator true
som definieras av typenb
för att skapa ettbool
värde.
- Om det finns en implicit konvertering från typen av
- Om det
bool
värde som skapas av steget ovan ärtrue
utvärderas och konverterasx
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örM
ärvoid
(§12.8.13). Men när den betraktas som en null_conditional_invocation_expressiontillåts resultattypen varavoid
. slutkommentar
Exempel: Resultattypen för
List<T>.Reverse
ärvoid
. 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 åtthis
. Detta gäller om åtkomsten är explicit (som ithis.x
) eller implicit (som ix
därx
ä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, enbreak
-instruktion eller encontinue
-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 ettselector
argument, som extraherar värdet som ska summeras från ett listobjekt. Det extraherade värdet kan antingen vara enint
eller endouble
och den resulterande summan är också antingen enint
eller endouble
.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 metodernaSum
giltiga eftersom den anonyma funktionend => d.UnitCount
är kompatibel med bådeFunc<Detail,int>
ochFunc<Detail,double>
. Överbelastningslösningen väljer dock den förstaSum
-metoden eftersom konverteringen tillFunc<Detail,int>
är bättre än konverteringen tillFunc<Detail,double>
.I den andra anropet av
orderDetails.Sum
gäller endast den andraSum
-metoden eftersom den anonyma funktionend => d.UnitPrice * d.UnitCount
genererar ett värde av typendouble
. Överbelastningsupplösningen väljer därför den andra metodenSum
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örx
förlängs, åtminstone tills delegeringen som returneras frånF
blir berättigad till skräpinsamling. Eftersom varje anrop av den anonyma funktionen fungerar på samma instans avx
ä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 avx
: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 tillstatic 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 avy
, 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 "from
identifierare" 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
, GroupBy
och 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ännaIEnumerable<T>
-gränssnittet. I exemplet ovan skulle detta vara fallet om kunderna var av typenArrayList
. 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örSelect
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
ochy
ä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
ochy
ä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>
ochO<T>
som säkerställer att metodernaThenBy
ochThenByDescending
endast är tillgängliga på resultatet av enOrderBy
ellerOrderByDescending
. slutkommentar
Obs: Den rekommenderade formen på resultatet av
GroupBy
– en sekvens med sekvenser, där varje inre sekvens har ytterligare enKey
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 implementerarSystem.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 soma = (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 dynamic
och 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)
ochy
kan dekonstrueras till ett tupppeluttryck(y1, ..., yn)
medn
element (§12.7), och varje tilldelning tillxi
avyi
har typenTi
, har tilldelningen typen(T1, ..., Tn)
. - Om
x
annars klassificeras som en variabel är variabeln intereadonly
,x
har en typT
ochy
har en implicit konvertering tillT
har tilldelningen typenT
. - Om
x
annars klassificeras som en implicit typvariabel (dvs. ett implicit skrivet deklarationsuttryck) ochy
har en typT
, är variabelns härledda typT
och tilldelningen har typenT
. - 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 typenT
, ochy
har en implicit konvertering tillT
, då har tilldelningen typenT
. - 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ärderasy
och konverteras vid behov tillT
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öry
är kompatibelt med matrisinstansen därx
är ett element. Kontrollen lyckas omy
ärnull
, eller om en implicit referenskonvertering (§10.2.8) finns från den typ av instans som refereras avy
till den faktiska elementtypen för matrisinstansen som innehållerx
. Annars utlöses enSystem.ArrayTypeMismatchException
. - Värdet som är resultatet av utvärderingen och konverteringen av
y
lagras på den plats som anges av utvärderingen avx
och returneras som ett resultat av tilldelningen.
- Om variabeln som anges av
- Om
x
klassificeras som åtkomst till en egenskap eller indexerare:-
y
utvärderas och konverteras vid behov tillT
genom en implicit konvertering (§10.2). - Set-accessorn för
x
anropas med värdet som är resultatet av utvärderingen och konverteringen avy
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 aritetn
.-
y
dekonstrueras medn
element till ett tupppeluttrycke
. - en resultattuple
t
skapas genom att konverterae
tillT
med hjälp av en implicit tuppelkonvertering. - För varje
xi
i ordning från vänster till höger utförs en tilldelning tillxi
avt.Itemi
, dock utvärderas intexi
igen. -
t
returneras som ett resultat av tilldelningen.
-
Obs!: om kompileringstidstypen för
x
ärdynamic
, och det finns en implicit omvandling från kompileringstidstypen föry
tilldynamic
, 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 matristypB[]
, förutsatt att det finns en implicit referenskonvertering frånB
tillA
. 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 exempletstring[] 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 enArrayList
inte kan lagras i ett element i enstring[]
.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.A
ochr.B
tillåts eftersomp
ochr
är variabler. Men i exempletRectangle 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
ochr.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äsaref
delen som en del av operanden. Detta är särskilt förvirrande när operanden är ett villkorsuttryck?:
. När du till exempel läserref int a = ref b ? ref x : ref y;
är det viktigt att läsa detta som att= ref
är operatorn ochb ? ref x : ref y
den högra operanden:ref int a = ref (b ? ref x : ref y);
. Det är viktigt att uttrycketref b
inte 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 dynamic
och 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
x
utvärderas åtgärden somx = x «op» y
, förutom attx
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 omy
implicit kan konverteras till typen avx
eller operatorn är en skiftoperator, utvärderas åtgärden somx = (T)(x «op» y)
, därT
är typen avx
, förutom attx
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» y
sparas 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ärA
är en metod som returnerarint[]
ochB
ochC
är metoder som returnerarint
, anropas metoderna bara en gång, i den ordningA
,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
, ushort
eller 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 antingenx = x «op» y
ellerx = (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
ochunchecked
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 somsizeof
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 avstr
ä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 enswitch
-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
, uint
eller 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 true
påE
, och den implementeringen tillämpas vid programkörningstid. - Om ingen sådan operator hittas uppstår ett bindningstidsfel.
ECMA C# draft specification