Delen via


12 Uitdrukkingen

12.1 Algemeen

Een expressie is een reeks operatoren en operanden. Met deze component worden de syntaxis, volgorde van evaluatie van operanden en operatoren en de betekenis van expressies gedefinieerd.

12.2 Uitdrukkingclassificaties

12.2.1 Algemeen

Het resultaat van een expressie wordt geclassificeerd als een van de volgende:

  • Een waarde. Elke waarde heeft een gekoppeld type.
  • Een variabele. Tenzij anders opgegeven, wordt een variabele expliciet getypt en heeft een gekoppeld type, namelijk het gedeclareerde type van de variabele. Een impliciet getypte variabele heeft geen gekoppeld type.
  • Een letterlijke waarde van null. Een expressie met deze classificatie kan impliciet worden geconverteerd naar een verwijzingstype of null-waardetype.
  • Een anonieme functie. Een expressie met deze classificatie kan impliciet worden geconverteerd naar een compatibel type delegeaat of expressieboomtype.
  • Een tuple. Elke tuple heeft een vast aantal elementen, elk met een expressie en een optionele tuple-elementnaam.
  • Toegang tot een eigenschap. Elke eigenschapstoegang heeft een gekoppeld type, namelijk het type eigenschap. Bovendien kan een eigenschapstoegang een bijbehorende instantie-expressie hebben. Wanneer een accessor van een exemplaareigenschapstoegang wordt aangeroepen, wordt het resultaat van het evalueren van de exemplaarexpressie het exemplaar dat wordt vertegenwoordigd door this (§12.8.14).
  • Toegang tot een indexeerfunctie. Elke indexeerfunctietoegang heeft een gekoppeld type, namelijk het elementtype van de indexeerfunctie. Bovendien heeft een indexeerfunctietoegang een bijbehorende exemplaarexpressie en een bijbehorende argumentenlijst. Wanneer een toegangsfunctie van een indexeerfunctie wordt aangeroepen, wordt het resultaat van het evalueren van de exemplaarexpressie het exemplaar dat wordt vertegenwoordigd door this (§12.8.14) en wordt het resultaat van het evalueren van de argumentenlijst de parameterlijst van de aanroep.
  • Niets. Dit gebeurt wanneer de expressie een aanroep van een methode is met een retourtype van void. Een expressie die als niets wordt geclassificeerd, is alleen geldig in de context van een statement_expression (§13.7) of als de hoofdtekst van een lambda_expression (§12.19).

Voor expressies die optreden als subexpressies van grotere expressies, met de genoteerde beperkingen, kan het resultaat ook worden geclassificeerd als een van de volgende:

  • Een naamruimte. Een expressie met deze classificatie kan alleen worden weergegeven als de linkerkant van een member_access (§12.8.7). In een andere context veroorzaakt een expressie die is geclassificeerd als een naamruimte een compileertijdfout.
  • Een type. Een expressie met deze classificatie kan alleen worden weergegeven als de linkerkant van een member_access (§12.8.7). In een andere context veroorzaakt een expressie die is geclassificeerd als een type een compileertijdfout.
  • Een methodegroep, een set overbelaste methoden die het gevolg zijn van een ledenopzoekactie (§12.5). Een methodegroep kan een bijbehorende exemplaarexpressie en een lijst met bijbehorende typeargumenten hebben. Wanneer een exemplaarmethode wordt aangeroepen, wordt het resultaat van het evalueren van de exemplaarexpressie het exemplaar dat wordt vertegenwoordigd door this (§12.8.14). Een methodegroep is toegestaan in een invocation_expression (§12.8.10) of een delegate_creation_expression (§12.8.17.6) en kan impliciet worden geconverteerd naar een compatibel gemachtigdetype (§10.8). In een andere context veroorzaakt een expressie die is geclassificeerd als een methodegroep een compilatiefout.
  • Toegang tot een evenement Elke gebeurtenistoegang heeft een gekoppeld type, namelijk het type gebeurtenis. Bovendien kan een gebeurtenistoegang een bijbehorende exemplaarexpressie hebben. Een gebeurtenistoegang kan worden weergegeven als de linkeroperand van de operatoren += en -= (§12.21.5). In een andere context veroorzaakt een expressie die is geclassificeerd als gebeurtenistoegang een compilatiefout. Wanneer een accessor van een exemplaargebeurtenistoegang wordt aangeroepen, wordt het resultaat van het evalueren van de exemplaarexpressie het exemplaar dat wordt vertegenwoordigd door this (§12.8.14).
  • Een throw-expressie, die in verschillende contexten kan worden gebruikt om een uitzondering in een expressie te genereren. Een throw-expressie kan worden geconverteerd door een impliciete conversie naar elk type.

Een toegang tot eigenschappen of indexertoegang wordt altijd opnieuw geclassificeerd als een waarde door de get-accessor of set-accessor aan te roepen. De specifieke toegangsfunctie wordt bepaald door de context van de toegang tot de eigenschap of indexeerfunctie: als de toegang het doel van een toewijzing is, wordt de ingestelde toegangsfunctie aangeroepen om een nieuwe waarde toe te wijzen (§12.21.2). Anders wordt de get accessor aangeroepen om de huidige waarde te verkrijgen (§12.2.2).

Een exemplaartoegangsfunctie is een eigenschapstoegang voor een exemplaar, een gebeurtenistoegang op een exemplaar of een indexeerfunctietoegang.

12.2.2 Waarden van expressies

De meeste constructies die betrekking hebben op een expressie, vereisen uiteindelijk dat de expressie een waarde aangeeft. Als de werkelijke expressie in dergelijke gevallen een naamruimte, een type, een methodegroep of niets aangeeft, treedt er een compilatietijdfout op. Als de expressie echter een eigenschapstoegang, een indexeerfunctietoegang of een variabele aangeeft, wordt de waarde van de eigenschap, indexeerfunctie of variabele impliciet vervangen:

  • De waarde van een variabele is gewoon de waarde die momenteel is opgeslagen in de opslaglocatie die wordt geïdentificeerd door de variabele. Een variabele wordt beschouwd als definitief toegewezen (§9.4) voordat de waarde ervan kan worden verkregen, of anders treedt er een compilatiefout op.
  • De waarde van een eigenschapstoegangsexpressie wordt verkregen door de get accessor van de eigenschap aan te roepen. Als de eigenschap geen get-accessor heeft, ontstaat er een compilatiefout. Anders wordt een functielid aanroep (§12.6.6) uitgevoerd en wordt het resultaat van de aanroep de waarde van de eigenschapstoegangsexpressie.
  • De waarde van een indexeerfunctie-toegangsexpressie wordt verkregen door de get-accessor van de indexeerfunctie aan te roepen. Als de indexer geen gettoegangsmethode heeft, ontstaat er een fout tijdens de compilatie. Anders wordt een functielid aanroep (§12.6.6) uitgevoerd met de argumentenlijst die is gekoppeld aan de toegangsexpressie van de indexeerfunctie en wordt het resultaat van de aanroep de waarde van de toegangsexpressie van de indexeerfunctie.
  • De waarde van een tuple-expressie wordt verkregen door een impliciete tupleconversie (§10.2.13) toe te passen op het type tuple-expressie. Het is een fout om de waarde van een tuple-expressie te achterhalen die geen type heeft.

12.3 Statische en dynamische binding

12.3.1 Algemeen

Binding is het proces van het bepalen waarnaar een bewerking verwijst, op basis van het type of de waarde van expressies (argumenten, operanden, ontvangers). De binding van een methode-aanroep wordt bijvoorbeeld bepaald op basis van het type ontvanger en argumenten. De binding van een operator wordt bepaald op basis van het type operanden.

In C# wordt de binding van een bewerking meestal bepaald tijdens het compileren, op basis van het compileertijdtype van de subexpressies. Als een expressie een fout bevat, wordt de fout ook gedetecteerd en gerapporteerd tijdens het compileren. Deze methode wordt statische bindinggenoemd.

Als een expressie echter een dynamische expressie is (d.w.z. heeft het type dynamic) geeft dit aan dat elke binding waaraan deze deelneemt, moet zijn gebaseerd op het runtimetype in plaats van het type dat het tijdens het compileren heeft. De binding van een dergelijke bewerking wordt daarom uitgesteld tot de tijd waarop de bewerking moet worden uitgevoerd tijdens het uitvoeren van het programma. Dit wordt dynamische bindinggenoemd.

Wanneer een bewerking dynamisch is gebonden, wordt tijdens het compileren weinig of geen controle uitgevoerd. Als de runtimebinding mislukt, worden fouten gerapporteerd als uitzonderingen tijdens runtime.

De volgende bewerkingen in C# zijn onderhevig aan binding:

  • Toegang tot leden: e.M
  • Aanroep van methode: e.M(e₁,...,eᵥ)
  • Aanroep delegeren: e(e₁,...,eᵥ)
  • Toegang tot elementen: e[e₁,...,eᵥ]
  • Object maken: nieuwe C(e₁,...,eᵥ)
  • Overbelaste unaire operatoren: +, -, ! (alleen logische negatie), ~, ++, --, true, false
  • Overbelaste binaire operatoren: +, -, *, /, %, &, &&, |, ||, ??, ^, <<, >>, ==, !=, >, <, >=, <=
  • Toewijzingsoperatoren: =, = ref, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=
  • Impliciete en expliciete conversies

Wanneer er geen dynamische expressies betrokken zijn, wordt in C# standaard ingesteld op statische binding, wat betekent dat de compileertijdtypen van subexpressies worden gebruikt in het selectieproces. Wanneer een van de subexpressies in de bovenstaande bewerkingen echter een dynamische expressie is, is de bewerking in plaats daarvan dynamisch gebonden.

Het is een compilatietijdfout als een methode-aanroep dynamisch is gebonden en een van de parameters, inclusief de ontvanger, invoerparameters zijn.

12.3.2 Bindingstijd

Statische binding vindt plaats tijdens het compileren, terwijl dynamische binding plaatsvindt tijdens runtime. In de volgende subclauses verwijst de term binding-time verwijst naar compileertijd of runtime, afhankelijk van wanneer de binding plaatsvindt.

Voorbeeld van: Hieronder ziet u de noties van statische en dynamische binding en bindingstijd:

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 eerste twee aanroepen zijn statisch gebonden: de overbelasting van Console.WriteLine wordt gekozen op basis van het type compileertijd van hun argument. De bindingstijd wordt dus compilatietijd.

De derde aanroep is dynamisch gebonden: de overbelasting van Console.WriteLine wordt gekozen op basis van het runtimetype van het argument. Dit gebeurt omdat het argument een dynamische expressie is: het type compileertijd is dynamisch. De bindingstijd voor de derde aanroep is dus uitvoeringstijd.

einde voorbeeld

12.3.3 Dynamische binding

Deze subclause is informatief.

Met dynamische binding kunnen C#-programma's communiceren met dynamische objecten, d.w.: objecten die niet voldoen aan de normale regels van het C#-typesysteem. Dynamische objecten kunnen objecten zijn uit andere programmeertalen met verschillende typen systemen, of ze kunnen objecten zijn die programmatisch zijn ingesteld om hun eigen bindingsemantiek voor verschillende bewerkingen te implementeren.

Het mechanisme waarmee een dynamisch object zijn eigen semantiek implementeert, is door de implementatie gedefinieerd. Een bepaalde interface, opnieuw door implementatie gedefinieerd, wordt geïmplementeerd door dynamische objecten om de C#-runtime te signaleren dat ze speciale semantiek hebben. Wanneer bewerkingen op een dynamisch object dus dynamisch zijn gebonden, nemen ze hun eigen bindingsemantiek over, in plaats van C# zoals opgegeven in deze specificatie.

Hoewel het doel van dynamische binding is om interoperation met dynamische objecten mogelijk te maken, staat C# dynamische binding toe op alle objecten, ongeacht of ze dynamisch zijn of niet. Dit maakt een soepelere integratie van dynamische objecten mogelijk, omdat de resultaten van bewerkingen op deze objecten mogelijk niet zelf dynamische objecten zijn, maar nog steeds van een type onbekend zijn voor de programmeur tijdens het compileren. Dynamische binding kan ook helpen bij het elimineren van foutgevoelige op reflectie gebaseerde code, zelfs wanneer de betrokken objecten geen dynamische objecten zijn.

12.3.4 Typen subexpressies

Wanneer een bewerking statisch is gebonden, wordt het type subexpressie (bijvoorbeeld een ontvanger en argument, een index of een operand) altijd beschouwd als het type compileertijd van die expressie.

Wanneer een bewerking dynamisch is gebonden, wordt het type subexpressie op verschillende manieren bepaald, afhankelijk van het type compileertijd van de subexpressie:

  • Een subexpressie van het dynamische type compileertijd wordt beschouwd als het type van de werkelijke waarde waarnaar de expressie tijdens runtime evalueert
  • Een subexpressie waarvan het compileertijdtype een typeparameter is, wordt beschouwd als het type waaraan de typeparameter bij uitvoering is gebonden.
  • Anders wordt de subexpressie geacht zijn compileertijd-type te hebben.

12.4 Operatoren

12.4.1 Algemeen

Expressies worden samengesteld op basis van operanden en operators. De operatoren van een expressie geven aan welke bewerkingen moeten worden toegepast op de operanden.

voorbeeld: voorbeelden van operators zijn onder andere +, -, *, /en new. Voorbeelden van operanden zijn letterlijke waarden, velden, lokale variabelen en expressies. einde voorbeeld

Er zijn drie soorten operators:

  • Unaire operatoren. De unaire operatoren nemen één operand en gebruiken een voorvoegsel notatie (zoals –x) of achtervoegsel notatie (zoals x++).
  • Binaire operatoren. De binaire operatoren nemen twee operanden en gebruiken allemaal infix-notatie (zoals x + y).
  • Ternaire operator. Slechts één ternaire operator, ?:, bestaat; het duurt drie operanden en maakt gebruik van infix notatie (c ? x : y).

De beoordelingsvolgorde van operators in een expressie wordt bepaald door de prioriteit en associatieve van de operatoren (§12.4.2).

Operanden in een expressie worden van links naar rechts geëvalueerd.

voorbeeld: in F(i) + G(i++) * H(i)wordt de methode F aangeroepen met behulp van de oude waarde van i, wordt de methode G aangeroepen met de oude waarde van ien tot slot wordt de methode H aangeroepen met de nieuwe waarde i. Dit is gescheiden van en niet gerelateerd aan de prioriteit van de operator. einde voorbeeld

Bepaalde operators kunnen worden overbelast. Bij overbelasting van operatoren (§12.4.3) kunnen door de gebruiker gedefinieerde operator-implementaties worden opgegeven voor bewerkingen waarbij een of beide operanden van een door de gebruiker gedefinieerde klasse of struct-type zijn.

12.4.2 Operatorprioriteit en associativiteit

Wanneer een expressie meerdere operators bevat, bepaalt de prioriteit van de operators de volgorde waarin de afzonderlijke operators worden geëvalueerd.

Opmerking: de expressie x + y * z wordt bijvoorbeeld geëvalueerd als x + (y * z) omdat de operator * een hogere prioriteit heeft dan de operator binaire +. eindnotitie

De prioriteit van een operator wordt bepaald door de definitie van de bijbehorende grammaticaproductie.

Opmerking: een additive_expression bestaat bijvoorbeeld uit een reeks multiplicative_expressiongescheiden door +- of - operatoren, waardoor de operatoren + en - lagere prioriteit hebben dan de operatoren *, /en %. eindnotitie

Opmerking: de volgende tabel bevat een overzicht van alle operatoren in volgorde van prioriteit van hoog naar laag:

subclausule categorie Operators
§12.8 Primair x.y x?.y f(x) a[x] a?[x] x++ x-- x! new typeof default checked unchecked delegate stackalloc
§12.9 Unaire + - !x ~ ++x --x (T)x await x
§12.10 Multiplicatieve * / %
§12.10 Toevoeging + -
§12.11 Verschuiving << >>
§12.12 Relationele en type-testen < > <= >= is as
§12.12 Gelijkheid == !=
§12.13 Logische AND &
§12.13 Logische XOR ^
§12.13 Logische OR \|
§12.14 Voorwaardelijk EN &&
§12.14 Voorwaardelijk OF \|\|
§12.15 en §12.16 Null-coalescing en gooi-expressie ?? throw x
§12.18 Voorwaardelijk ?:
§12.21 en §12.19 Toewijzing en lambda-expressie = = ref *= /= %= += -= <<= >>= &= ^= \|= =>

eindnotitie

Wanneer een operand plaatsvindt tussen twee operators met dezelfde prioriteit, bepaalt de associativiteit van de operators de volgorde waarin de bewerkingen worden uitgevoerd:

  • Met uitzondering van de toewijzingsoperatoren en de null-coalescentie-operator zijn alle binaire operatoren links-associatief, wat betekent dat bewerkingen van links naar rechts worden uitgevoerd.

    voorbeeld van: x + y + z wordt geëvalueerd als (x + y) + z. einde voorbeeld

  • De toewijzingsoperatoren, de samensnookoperator null en de voorwaardelijke operator (?:) zijn rechts-associatieve, wat betekent dat bewerkingen van rechts naar links worden uitgevoerd.

    voorbeeld van: x = y = z wordt geëvalueerd als x = (y = z). einde voorbeeld

Prioriteit en associativiteit kunnen worden beheerd met haakjes.

voorbeeld: x + y * z vermenigvuldigt eerst y met z en voegt vervolgens het resultaat toe aan x, maar (x + y) * z voegt eerst x en y toe en vermenigvuldigt vervolgens het resultaat met z. einde voorbeeld

12.4.3 Operator-overbelasting

Alle unaire en binaire operators hebben vooraf gedefinieerde implementaties. Daarnaast kunnen door de gebruiker gedefinieerde implementaties worden ingevoerd door operatordeclaraties (§15.10) in klassen en structs op te geven. Door de gebruiker gedefinieerde operator-implementaties hebben altijd voorrang op vooraf gedefinieerde operator-implementaties: Alleen wanneer er geen toepasselijke door de gebruiker gedefinieerde operator-implementaties bestaan, worden de vooraf gedefinieerde implementaties van operatoren beschouwd, zoals beschreven in §12.4.4 en §12.4.5.

De overbelaste unaire operators zijn:

+ - ! (alleen logische ontkenning) ~ ++ -- true false

Opmerking: Hoewel true en false niet expliciet worden gebruikt in expressies (en daarom niet zijn opgenomen in de prioriteitstabel in §12.4.2), worden ze beschouwd als operatoren omdat ze in verschillende expressiecontexten worden aangeroepen : Booleaanse expressies (§12.24) en expressies met betrekking tot de voorwaardelijk (§12.18) en voorwaardelijke logische operatoren (§12.14). eindnotitie

Opmerking: de operator null-forgiving (postfix !, §12.8.9) is geen overbelastingsbare operator. eindnotitie

De overbelastingsbare binaire operatoren zijn:

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

Alleen de hierboven genoemde operators kunnen overbelast worden. Met name is het niet mogelijk om toegang tot leden, methodeaanroepen of de operators =, &&, ||, ??, ?:, =>, checked, unchecked, new, typeof, default, asen is te overbelasten.

Wanneer een binaire operator overbelast is, wordt de bijbehorende samengestelde toewijzingsoperator, indien aanwezig, ook impliciet overbelast.

Voorbeeld: een overbelasting van operatoren * is ook een overbelasting van operatoren *=. Dit wordt verder beschreven in §12.21. einde voorbeeld

De toewijzingsoperator zelf (=) kan niet worden overbelast. Een toewijzing voert altijd een eenvoudige opslag van een waarde uit in een variabele (§12.21.2).

Cast-bewerkingen, zoals (T)x, worden overbelast door door de gebruiker gedefinieerde conversies (§10,5).

Opmerking: door de gebruiker gedefinieerde conversies hebben geen invloed op het gedrag van de operators voor is of as. eindnotitie

Toegang tot elementen, zoals a[x], wordt niet beschouwd als een overloadbare operator. In plaats daarvan wordt door de gebruiker gedefinieerde indexering ondersteund via indexeerfuncties (§15,9).

In expressies wordt naar operators verwezen met behulp van operator-notatie en in declaraties wordt naar operators verwezen met behulp van functionele notatie. In de volgende tabel ziet u de relatie tussen operator- en functionele notaties voor unaire en binaire operatoren. In het eerste item geeft «op» een overbelastingsbare unary-voorvoegseloperator aan. In de tweede vermelding geeft «op» de unary postfix ++ en -- operators aan. In de derde vermelding geeft «op» elke overbelastingsbare binaire operator aan.

Opmerking: Voor een voorbeeld van het overbelasten van de operatoren ++ en --, zie §15.10.2. eindnotitie

Operator-notatie functionele notatie
«op» x operator «op»(x)
x «op» operator «op»(x)
x «op» y operator «op»(x, y)

Door de gebruiker gedefinieerde operatordeclaraties vereisen altijd ten minste één van de parameters van het klasse- of structtype dat de operatordeclaratie bevat.

Opmerking: het is dus niet mogelijk dat een door de gebruiker gedefinieerde operator dezelfde handtekening heeft als een vooraf gedefinieerde operator. eindnotitie

Door de gebruiker gedefinieerde operatordeclaraties kunnen de syntaxis, prioriteit of associativiteit van een operator niet wijzigen.

voorbeeld: de operator / is altijd een binaire operator, heeft altijd het prioriteitsniveau dat is opgegeven in §12.4.2en is altijd links-associatief. einde voorbeeld

Opmerking: hoewel het mogelijk is voor een door de gebruiker gedefinieerde operator om berekeningen uit te voeren, worden implementaties die andere resultaten opleveren dan de implementaties die intuïtief worden verwacht sterk afgeraden. Een implementatie van operator == moet bijvoorbeeld de twee operanden voor gelijkheid vergelijken en een geschikt bool resultaat retourneren. eindnotitie

De beschrijvingen van individuele exploitanten in §12.9 tot en met §12.21 de vooraf gedefinieerde implementaties van de operators en eventuele aanvullende regels opgeven die van toepassing zijn op elke operator. De beschrijvingen maken gebruik van de termen unaire operator overbelastingsresolutie, binaire operator overbelastingsresolutie, numerieke promotieen definities van opgeheven operatoren die in de volgende subclauses worden gevonden.

12.4.4 Oplossing voor unaire operatoroverbelasting

Een werking van het formulier «op» x of x «op», waarbij «op» een overbelaste unaire operator is en x een expressie van het type Xis, wordt als volgt verwerkt:

  • De set door de gebruiker gedefinieerde kandidaatoperators die door X worden verstrekt voor de bewerking operator «op»(x) wordt bepaald aan de hand van de regels van §12.4.6.
  • Als de set door de gebruiker gedefinieerde kandidaatoperators niet leeg is, wordt dit de set kandidaatoperators voor de bewerking. Anders vormen de vooraf gedefinieerde binaire operator «op»-implementaties, inclusief hun aangepaste vormen, de verzameling kandidaatoperatoren voor de bewerking. De vooraf gedefinieerde implementaties van een bepaalde operator worden opgegeven in de beschrijving van de operator. De vooraf gedefinieerde operators die worden geleverd door een enum- of delegatetype, worden alleen opgenomen in deze set wanneer het bindingstijdtype (of het onderliggende type als het een nullable type is) van een operand het enum- of delegatetype is.
  • De regels voor overbelastingsoplossing van §12.6.4 worden toegepast op de set kandidaatoperatoren om de beste operator te selecteren met betrekking tot de lijst met argumenten (x), en deze operator wordt het resultaat van het overbelastingsoplossingsproces. Als overbelastingsresolutie niet één beste operator selecteert, treedt er een bindingstijdfout op.

12.4.5 Binaire operator overbelastingsresolutie

Een werking van het formulier x «op» y, waarbij «op» een overbelastingbare binaire operator is, x een expressie van het type Xis en y een expressie van het type Yis, wordt als volgt verwerkt:

  • De set door de gebruiker gedefinieerde kandidaatoperators die worden geleverd door X en Y voor de bewerking operator «op»(x, y) wordt bepaald. De set bestaat uit de vereniging van de door X en Yverstrekte kandidaat-operators, die elk worden bepaald volgens de regels van §12.4.6. Voor de gecombineerde set worden kandidaten als volgt samengevoegd:
    • Als X en Y identiteit converteerbaar zijn of als X en Y zijn afgeleid van een gemeenschappelijk basistype, worden gedeelde kandidaatoperators slechts eenmaal in de gecombineerde set uitgevoerd.
    • Als er een identiteitsconversie is tussen X en Y, heeft een operator «op»Y van Y hetzelfde retourtype als een «op»X geleverd door X en de operandtypen van «op»Y een identiteitsconversie hebben naar de bijbehorende operandtypen van «op»X dan vindt er slechts «op»X plaats in de set.
  • Als de set door de gebruiker gedefinieerde kandidaatoperators niet leeg is, wordt dit de set kandidaatoperators voor de bewerking. Anders vormen de vooraf gedefinieerde binaire operator «op»-implementaties, inclusief hun aangepaste vormen, de verzameling kandidaatoperatoren voor de bewerking. De vooraf gedefinieerde implementaties van een bepaalde operator worden opgegeven in de beschrijving van de operator. Voor voorgedefinieerde enum- en delegate-operatoren zijn de enige operators die worden beschouwd die door een enum- of delegatetype worden geleverd dat het bindingstijdtype van een van de operanden is.
  • De regels voor overbelastingsoplossing van §12.6.4 worden toegepast op de set kandidaatoperatoren om de beste operator te selecteren met betrekking tot de lijst met argumenten (x, y), en deze operator wordt het resultaat van het overbelastingsoplossingsproces. Als overbelastingsresolutie niet één beste operator selecteert, treedt er een bindingstijdfout op.

12.4.6 Door de gebruiker gedefinieerde kandidaatoperators

Gezien een type T en een bewerking operator «op»(A), waarbij «op» een overbelastingsbare operator is en A een lijst met argumenten is, wordt de set door de gebruiker gedefinieerde kandidaatoperators die door T worden verstrekt voor operator-«op»(A) als volgt bepaald:

  • Het type T₀bepalen. Als T een nullable waarde type is, dan is T₀ het onderliggende type; anders is T₀ gelijk aan T.
  • Voor alle operator «op» declaraties in T₀ en alle opgeheven vormen van dergelijke exploitanten, indien ten minste één exploitant van toepassing is (§12.6.4.2) met betrekking tot de lijst met argumenten A, bestaat de set kandidaat-exploitanten uit alle toepasselijke exploitanten in T₀.
  • Als T₀ anders objectis, is de set van kandidaat-operators leeg.
  • Anders is de set kandidaat-operators die worden geleverd door T₀ de set kandidaat-operators die worden geleverd door de directe basisklasse van T₀, of de effectieve basisklasse van T₀ als T₀ een typeparameter is.

12.4.7 Numerieke promoties

12.4.7.1 Algemeen

Deze subclause is informatief.

§12.4.7 en de subclauses ervan zijn een samenvatting van het gecombineerde effect van:

  • de regels voor impliciete numerieke conversies (§10.2.3);
  • de regels voor een betere omzetting (§12.6.4.7); en
  • de beschikbare rekenkundige (§12.10), relationele (§12.12) en integraal logische operatoren (§12.13.2).

Numerieke promotie bestaat uit het automatisch uitvoeren van bepaalde impliciete conversies van de operanden van de vooraf gedefinieerde unaire en binaire numerieke operatoren. Numerieke promotie is geen afzonderlijk mechanisme, maar een effect van het toepassen van overbelastingsresolutie op de vooraf gedefinieerde operators. Numerieke promotie heeft specifiek geen invloed op de evaluatie van door de gebruiker gedefinieerde operators, hoewel door de gebruiker gedefinieerde operators kunnen worden geïmplementeerd om vergelijkbare effecten te vertonen.

Als voorbeeld van numerieke promotie kunt u rekening houden met de vooraf gedefinieerde implementaties van de binaire *-operator:

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

Wanneer overbelastingsoplossingsregels (§12.6.4) worden toegepast op deze set operators, is het effect het selecteren van de eerste operatoren waarvoor impliciete conversies bestaan uit de operandtypen.

voorbeeld: voor de bewerking b * s, waarbij b een byte is en s een shortis, selecteert overbelastingsresolutie operator *(int, int) als de beste operator. Het effect is dus dat b en s worden geconverteerd naar int, en het type van het resultaat is int. Voor de bewerking i * d, waarbij i een int is en d een doubleis, selecteert overload resolutie operator *(double, double) als de beste operator. einde voorbeeld

einde van informatieve tekst.

12.4.7.2 Unaire numerieke promoties

Deze subclause is informatief.

Unaire numerieke promotie vindt plaats voor de operanden van de vooraf gedefinieerde +, -en ~ unaire operatoren. Unaire numerieke promotie bestaat eenvoudig uit het converteren van operanden van het type sbyte, byte, short, ushortof char om intte typen . Daarnaast converteert de unaire numerieke promotie operanden van het type uint naar type longvoor de unaire – operator.

einde van informatieve tekst.

12.4.7.3 Binaire numerieke promoties

Deze subclause is informatief.

Binaire numerieke promotie vindt plaats voor de operanden van de vooraf gedefinieerde +, -, *, /, %, &, |, ^, ==, !=, >, <, >=en <= binaire operatoren. Binaire numerieke promotie converteert impliciet beide operanden naar een gemeenschappelijk type dat, in het geval van de niet-relationele operatoren, ook het resultaattype van de bewerking wordt. Binaire numerieke promotie bestaat uit het toepassen van de volgende regels, in de volgorde waarin ze hier worden weergegeven:

  • Als een operand van het type decimalis, wordt de andere operand geconverteerd naar het type decimalof treedt er een bindingstijdfout op als de andere operand van het type float of doubleis.
  • Als een van beide operanden van het type doubleis, wordt de andere operand geconverteerd naar het type double.
  • Als een van beide operanden van het type floatis, wordt de andere operand geconverteerd naar het type float.
  • Als een operand van het type ulongis, wordt de andere operand geconverteerd naar het type ulongof treedt er een bindingstijdfout op als de andere operand van type sbyte, short, intof longis.
  • Als een van beide operanden van het type longis, wordt de andere operand geconverteerd naar het type long.
  • Als een operand van het type uint is en de andere operand van het type sbyte, shortof int, worden beide operanden geconverteerd naar het type long.
  • Als een van beide operanden van het type uintis, wordt de andere operand geconverteerd naar het type uint.
  • Anders worden beide operanden geconverteerd naar het type int.

Opmerking: De eerste regel verbiedt bewerkingen die het decimal-type combineren met de typen double en float. De regel volgt uit het feit dat er geen impliciete conversies zijn tussen het decimal type en de double en float typen. eindnotitie

Opmerking: houd er ook rekening mee dat het niet mogelijk is dat een operand van het type ulong is wanneer de andere operand van een ondertekend integraal type is. De reden hiervoor is dat er geen integraal type bestaat dat het volledige bereik van ulong en de ondertekende integrale typen kan vertegenwoordigen. eindnotitie

In beide bovenstaande gevallen kan een cast-expressie worden gebruikt om één operand expliciet te converteren naar een type dat compatibel is met de andere operand.

Voorbeeld: In de volgende code

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

er treedt een bindingstijdfout op omdat een decimal niet kan worden vermenigvuldigd met een double. De fout wordt opgelost door de tweede operand expliciet te converteren naar decimal, als volgt:

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

einde voorbeeld

einde van informatieve tekst.

12.4.8 Gelifte operatoren

Verhoogde operators staan vooraf gedefinieerde en door de gebruiker gedefinieerde operators toe die werken op niet-nullable waardetypen om ook met nullable vormen van deze typen te worden gebruikt. Lifted operators zijn samengesteld uit vooraf gedefinieerde en door de gebruiker gedefinieerde operators die voldoen aan bepaalde vereisten, zoals hieronder beschreven.

  • Voor de unaire operatoren +, ++, -, --, !(logische negatie) en ~, bestaat er een opgeheven vorm van een operator als de operand- en resultaattypen beide niet-nullbare waardetypen zijn. Het opgeheven formulier wordt samengesteld door één ? modificator toe te voegen aan operand- en resultaattypen. De gelifte operator produceert een null-waarde als de operand nullis. Anders haalt de lifted operator de operand uit, past de onderliggende operator toe en verpakt het resultaat.
  • Voor de binaire operatoren +, -, *, /, %, &, |, ^, <<en >>, bestaat er een opgeheven vorm van een operator als de operand- en resultaattypen allemaal niet-nullbare waardetypen zijn. Het opgeheven formulier wordt samengesteld door één ? modifier toe te voegen aan elke operand en het resultaattype. De opgetilde operator produceert een null waarde als een of beide operanden null zijn (een uitzondering: de operatoren & en | van het type bool?, zoals beschreven in §12.13.5). Anders haalt de opgetilde operator de operanden uit, past de onderliggende operator toe en verpakt het resultaat.
  • Voor de gelijkheidsoperatoren == en !=bestaat er een getilde vorm van een operator als de operandtypen zowel niet-nulwaarde-typen zijn, én het resultaattype is bool. Het opgeheven formulier wordt samengesteld door één ? modifier toe te voegen aan elk operandtype. De gelifte operator beschouwt twee null-waarden als gelijk en een null-waarde als ongelijk aan elke waarde die geennull is. Als beide operanden niet-nullzijn, haalt de lifted operator de operanden uit en past de onderliggende operator toe om het bool resultaat te produceren.
  • Voor de relationele operatoren <, >, <=en >=, bestaat er een opgeheven vorm van een operator als de operandtypen zowel niet-nullable waardetypen zijn als het resultaattype is bool. Het opgeheven formulier wordt samengesteld door één ? modifier toe te voegen aan elk operandtype. De gelifte operator levert de waarde false als een of beide operanden nullzijn. Anders pakt de lifted operator de operanden uit en past de onderliggende bewerking toe om het resultaat bool te verkrijgen.

12.5 Ledenzoekactie

12.5.1 Algemeen

Een ledenopzoekactie is het proces waarbij de betekenis van een naam in de context van een type wordt vastgesteld. Een lidzoekactie kan optreden als onderdeel van het evalueren van een simple_name (§12.8.4) of een member_access (§12.8.7) in een expressie. Als de simple_name of member_access optreedt als de primary_expression van een invocation_expression (§12.8.10.2), wordt het lid aangeroepen.

Als een lid een methode of gebeurtenis is, of als het een constante, veld of eigenschap is van een gemachtigdetype (§20) of het type dynamic (§8.2.4), wordt het lid aanroepbaar geacht.

Bij het opzoeken van leden wordt niet alleen rekening gebracht met de naam van een lid, maar ook met het aantal parameters van het type dat het lid heeft en of het lid toegankelijk is. Voor het opzoeken van leden hebben algemene methoden en geneste algemene typen het aantal typeparameters aangegeven in hun respectieve declaraties en alle andere leden hebben nultypeparameters.

Het opzoeken van een lid met de naam N met K typeargumenten in een type T wordt als volgt verwerkt:

  • Eerst wordt een set toegankelijke leden met de naam N bepaald:
    • Als T een typeparameter is, is de set de samenvoeging van de sets toegankelijke leden met de naam N in elk van de typen die zijn opgegeven als een primaire beperking of secundaire beperking (§15.2.5) voor T, samen met de set toegankelijke leden met de naam N in object.
    • Anders bestaat de set uit alle toegankelijke leden (§7,5) met de naam N in T, inclusief overgenomen leden en de toegankelijke leden met de naam N in object. Als T een samengesteld type is, wordt de set leden verkregen door typeargumenten te vervangen zoals beschreven in §15.3.3. Leden met een override modifier worden uitgesloten van de set.
  • Als K nul is, worden alle geneste typen waarvan de declaraties typeparameters bevatten, verwijderd. Als K niet nul is, worden alle leden met een ander aantal typeparameters verwijderd. Wanneer K nul is, worden methoden met typeparameters niet verwijderd, omdat het typedeductieproces (§12.6.3) mogelijk de typeargumenten kan afleiden.
  • Als het lid vervolgens wordt aangeroepen, worden alle niet-aanroepbare leden uit de set verwijderd.
  • Vervolgens worden leden die door andere leden zijn verborgen, uit de set verwijderd. Voor elk lid S.M in de set, waarbij S het type is waarin het lid M wordt gedeclareerd, worden de volgende regels toegepast:
    • Als M een constant, veld, eigenschap, gebeurtenis of opsommingslid is, worden alle leden die zijn gedeclareerd in een basistype S uit de set verwijderd.
    • Als M een typedeclaratie is, worden alle niet-typen die zijn gedeclareerd in een basistype van S uit de set verwijderd en worden alle typedeclaraties met hetzelfde aantal typeparameters verwijderd als M gedeclareerd in een basistype van S uit de set worden verwijderd.
    • Als M een methode is, worden alle niet-methodeleden die zijn gedeclareerd in een basistype van S uit de set verwijderd.
  • Vervolgens worden interfaceleden die door klasseleden worden verborgen, uit de set verwijderd. Deze stap heeft alleen een effect als T een typeparameter is en T zowel een effectieve basisklasse heeft dan object als een niet-lege effectieve interfaceset (§15.2.5). Voor elk lid S.M in de set, waarbij S het type is waarin het lid M wordt gedeclareerd, worden de volgende regels toegepast als S een andere klassedeclaratie is dan object:
    • Als M een constante, veld, eigenschap, gebeurtenis, opsommingslid of typedeclaratie is, worden alle leden die in een interfacedeclaratie zijn gedeclareerd, verwijderd uit de set.
    • Als M een methode is, worden alle niet-methodeleden die zijn gedeclareerd in een interfacedeclaratie uit de set verwijderd en worden alle methoden met dezelfde handtekening als M die zijn gedeclareerd in een interfacedeclaratie uit de set verwijderd.
  • Ten slotte wordt het resultaat van de zoekactie bepaald nadat verborgen leden zijn verwijderd:
    • Als de set bestaat uit één lid dat geen methode is, is dit lid het resultaat van de opzoekactie.
    • Als de set alleen methoden bevat, is deze groep methoden het resultaat van de opzoekactie.
    • Anders is de zoekactie dubbelzinnig en treedt er een bindingstijdfout op.

Voor lidzoekacties in typen anders dan typeparameters en interfaces, evenals lidzoekacties in interfaces die strikt sprake zijn van enkele overerving (waarbij elke interface in de overnameketen precies nul of één directe basisinterface heeft), is het effect van de opzoekregels simpelweg dat afgeleide leden basiselementen met dezelfde naam of handtekening verbergen. Dergelijke opzoekingen met enkele overerving zijn nooit dubbelzinnig. De dubbelzinnigheden die mogelijk kunnen ontstaan door lidzoekopdrachten in interfaces met meerdere overnames, worden beschreven in §18.4.6.

Opmerking: Deze fase houdt slechts rekening met één soort dubbelzinnigheid. Als het opzoeken van leden resulteert in een methodegroep, kan het gebruik van een methodegroep mislukken vanwege dubbelzinnigheid, zoals beschreven in §12.6.4.1 en §12.6.6.2. eindnotitie

12.5.2 Basistypen

Voor het opzoeken van leden wordt een type T geacht te bestaan uit de volgende basistypen:

  • Als T is object of dynamic, heeft T geen basistype.
  • Als T een enum_typeis, zijn de basistypen van T de klassetypen System.Enum, System.ValueTypeen object.
  • Als T een struct_typeis, zijn de basistypen van T de klassetypen System.ValueType en object.

    Opmerking: een nullable_value_type is een struct_type (§8.3.1). eindnotitie

  • Als T een class_typeis, zijn de basistypen van T de basisklassen van T, inclusief het klassetype object.
  • Als T een interface_typeis, zijn de basistypen van T de basisinterfaces van T en het klassetype object.
  • Als T een array_typeis, zijn de basistypen van T de klassetypen System.Array en object.
  • Als T een delegate_typeis, zijn de basistypen van T de klassetypen System.Delegate en object.

12.6 Functieleden

12.6.1 Algemeen

Functieleden zijn leden die uitvoerbare instructies bevatten. Functieleden zijn altijd leden van typen en kunnen geen lid zijn van naamruimten. C# definieert de volgende categorieën functieleden:

  • Methoden
  • Eigenschappen
  • Gebeurtenissen
  • Indexeerders
  • Door de gebruiker gedefinieerde operators
  • Exemplaarconstructors
  • Statische constructors
  • Finaliseerders

Met uitzondering van finalizers en statische constructors (die niet expliciet kunnen worden aangeroepen), worden de instructies in functieleden uitgevoerd via aanroepen van functieleden. De werkelijke syntaxis voor het schrijven van een aanroep van een functielid is afhankelijk van de specifieke categorie van het functielid.

De argumentenlijst (§12.6.2) van een aanroep van een functielid bevat werkelijke waarden of variabele verwijzingen voor de parameters van het functielid.

Aanroepen van algemene methoden kunnen gebruikmaken van typedeductie om de set typeargumenten te bepalen die aan de methode moeten worden doorgegeven. Dit proces wordt beschreven in §12.6.3.

Aanroepen van methoden, indexeerfuncties, operators en instantieconstructors maken gebruik van overbelastingsresolutie om te bepalen welke van een kandidaat-set functieleden moet worden aangeroepen. Dit proces wordt beschreven in §12.6.4.

Zodra een bepaald functielid tijdens bindingstijd is geïdentificeerd, mogelijk via overbelastingsresolutie, wordt het werkelijke runtimeproces voor het aanroepen van het functielid beschreven in §12.6.6.

Opmerking: de volgende tabel bevat een overzicht van de verwerking die plaatsvindt in constructies met de zes categorieën functieleden die expliciet kunnen worden aangeroepen. In de tabel geven e, x, yen value expressies aan die zijn geclassificeerd als variabelen of waarden, T een expressie aangeeft die is geclassificeerd als een type, F de eenvoudige naam van een methode is en P de eenvoudige naam van een eigenschap is.

Bouwen Voorbeeld Beschrijving
Methode-aanroep F(x, y) Overbelastingsresolutie wordt toegepast om de beste methode F in de bevatte klasse of structuur te selecteren. De methode wordt aangeroepen met de argumentenlijst (x, y). Als de methode niet staticis, is de instantie-expressie this.
T.F(x, y) Overbelastingsresolutie wordt toegepast om de beste methode te selecteren F in de klasse of struct T. Er treedt een fout op tijdens de bindtijd als de methode niet staticis. De methode wordt aangeroepen met de argumentenlijst (x, y).
e.F(x, y) Overbelastingresolutie wordt toegepast om de beste methode F in de klasse, struct of interface te selecteren die is opgegeven door het type e. Er treedt een bindingstijdfout op als de methode is static. De methode wordt aangeroepen met de exemplaarexpressie e en de lijst met argumenten (x, y).
Toegang tot eigenschappen P De get accessor van de eigenschap P in de bevatde klasse of struct wordt aangeroepen. Er treedt een compilatiefout op als P schrijf-alleen is. Als P niet staticis, is de exemplaarexpressie this.
P = value De set accessor van de eigenschap P in de bevatde klasse of struct wordt aangeroepen met de lijst met argumenten (value). Er treedt een compilatiefout op als P alleen-lezen is. Als P niet staticis, is de exemplaarexpressie this.
T.P De get accessor van de eigenschap P in de klasse of struct T wordt aangeroepen. Er treedt een compilatiefout op als P niet static is of als P alleen-voor-schrijven is.
T.P = value De set accessor van de eigenschap P in de klasse of struct T wordt aangeroepen met de argumentenlijst (value). Er treedt een compilatiefout op als P niet static is of als P alleen lezen is.
e.P De get accessor van de eigenschap P in de klasse, struct of interface die wordt opgegeven door het type E wordt aangeroepen met de exemplaarexpressie e. Er treedt een bindingstijdfout op als Pstatic is of als P alleen voor schrijven is.
e.P = value De settoegangsfunctie van de eigenschap P in de klasse, struct of interface die wordt opgegeven door het type E wordt aangeroepen met de exemplaarexpressie e en de lijst met argumenten (value). Er treedt een bindingstijdfout op als P is static of als P alleen-lezen is.
Toegang tot gebeurtenissen E += value De add accessor van de gebeurtenis E in de bevatde klasse of struct wordt aangeroepen. Als E niet staticis, is de exemplaarexpressie this.
E -= value De verwijdertoegangsfunctie van de gebeurtenis E in de bevattende Klasse of struct wordt aangeroepen. Als E niet staticis, is de exemplaarexpressie this.
T.E += value De add accessor van de gebeurtenis E in de klasse of struct T wordt aangeroepen. Er treedt een bindingstijdfout op als E niet is static.
T.E -= value De remove-toegangsfunctie van de gebeurtenis E in de klasse of struct T wordt aangeroepen. Er treedt een bindingstijdfout op als E niet is static.
e.E += value De add accessor van de gebeurtenis E in de klasse, struct of interface die wordt opgegeven door het type E, wordt aangeroepen met de expressie van de instantie e. Er treedt een bindingstijdfout op als E is static.
e.E -= value De verwijdertoegangsmethode van de gebeurtenis E in de klasse, struct of interface, zoals gegeven door het type E, wordt aangeroepen met de exemplaarexpressie e. Er treedt een bindingstijdfout op als E is static.
Toegang tot indexeerfunctie e[x, y] Overbelasting resolutie is van toepassing voor het selecteren van de beste indexer in de klasse, struct of interface die wordt gegeven door het type e. De get accessor van de indexeerfunctie wordt aangeroepen met de exemplaarexpressie e en de argumentenlijst (x, y). Er treedt een bindingstijdfout op als de indexeerfunctie alleen-schrijven is.
e[x, y] = value Overbelasting resolutie is van toepassing voor het selecteren van de beste indexer in de klasse, struct of interface die wordt gegeven door het type e. De set accessor van de indexeerfunctie wordt aangeroepen met de exemplaarexpressie e en de argumentenlijst (x, y, value). Er treedt een bindingstijdfout op als de indexeerfunctie alleen-lezen is.
Operator aanroepen -x Overbelastingsresolutie wordt toegepast om de beste unaire operator in de klasse of struct te selecteren die is opgegeven door het type x. De geselecteerde operator wordt aangeroepen met de lijst met argumenten (x).
x + y Overbelastingsresolutie wordt toegepast om de beste binaire operator te selecteren in de klassen of structs die worden gegeven door de typen x en y. De geselecteerde operator wordt aangeroepen met de lijst met argumenten (x, y).
Aanroep van exemplaarconstructor new T(x, y) Overbelastingsresolutie wordt toegepast om de beste instantieconstructor in de klasse of struct Tte selecteren. De instantieconstructor wordt aangeroepen met de lijst met argumenten (x, y).

eindnotitie

12.6.2 Argumentlijsten

12.6.2.1 Algemeen

Elk functielid en gemachtigde aanroep bevat een lijst met argumenten, die werkelijke waarden of variabele verwijzingen biedt voor de parameters van het functielid. De syntaxis voor het opgeven van de argumentenlijst van een aanroep van een functielid is afhankelijk van de categorie functielid:

  • Voor bijvoorbeeld constructors, methoden, indexeerfuncties en gemachtigden worden de argumenten opgegeven als een argument_list, zoals hieronder wordt beschreven. Voor indexeerders, bij het aanroepen van de set-accessor, bevat de argumentenlijst ook de expressie die is opgegeven als de rechteroperand van de toewijzingsoperator.

    Opmerking: dit extra argument wordt niet gebruikt voor overload-resolutie, alleen tijdens het aanroepen van de set-accessor. eindnotitie

  • Voor eigenschappen is de lijst met argumenten leeg bij het aanroepen van de get-accessor en bestaat deze uit de expressie die is opgegeven als de rechteroperand van de toewijzingsoperator bij het aanroepen van de set-accessor.
  • Voor gebeurtenissen bestaat de lijst met argumenten uit de expressie die is opgegeven als de rechteroperand van de operator += of -=.
  • Voor door de gebruiker gedefinieerde operators bestaat de lijst met argumenten uit één operand van de unaire operator of de twee operanden van de binaire operator.

De argumenten van eigenschappen (§15,7) en gebeurtenissen (§15,8) worden altijd doorgegeven als waardeparameters (§15.6.2.2). De argumenten van door de gebruiker gedefinieerde operatoren (§15.10) worden altijd doorgegeven als waardeparameters (§15.6.2.2) of invoerparameters (§9.2.8). De argumenten van indexeerfuncties (§15,9) worden altijd doorgegeven als waardeparameters (§15.6.2.2), invoerparameters (§9.2.8), of parametermatrices (§15.6.2.4). Uitvoer- en referentieparameters worden niet ondersteund voor deze categorieën functieleden.

De argumenten van een instantieconstructor, methode, indexeerfunctie of gemachtigde aanroep worden opgegeven als een 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
    ;

Een argument_list bestaat uit een of meer arguments, gescheiden door komma's. Elk argument bestaat uit een optionele argument_name gevolgd door een argument_value. Een argument met een argument_name wordt een benoemd argumentgenoemd, terwijl een argument zonder argument_name een positioneel argumentis.

De argument_value kan een van de volgende vormen aannemen:

  • Een expressie, waarmee wordt aangegeven dat het argument wordt doorgegeven als waardeparameter of wordt omgezet in een invoerparameter en vervolgens wordt doorgegeven als die, zoals bepaald door (§12.6.4.2 en wordt beschreven in §12.6.2.3.
  • Het trefwoord in gevolgd door een variable_reference (§9,5), waarmee wordt aangegeven dat het argument wordt doorgegeven als invoerparameter (§15.6.2.3.2). Een variabele moet zeker worden toegewezen (§9.4) voordat deze kan worden doorgegeven als invoerparameter.
  • Het trefwoord ref gevolgd door een variable_reference (§9,5), waarmee wordt aangegeven dat het argument wordt doorgegeven als referentieparameter (§15.6.2.3.3). Een variabele moet zeker worden toegewezen (§9.4) voordat deze kan worden doorgegeven als referentieparameter.
  • Het trefwoord out gevolgd door een variable_reference (§9,5), waarmee wordt aangegeven dat het argument wordt doorgegeven als een uitvoerparameter (§15.6.2.3.4). Een variabele wordt beschouwd als definitief toegewezen (§9.4) na een functielidaanroep waarin de variabele wordt doorgegeven als een uitvoerparameter.

Het formulier bepaalt de modus voor het doorgeven van parameters van het argument: respectievelijk waarde, invoer, verwijzingof uitvoer. Zoals hierboven vermeld, kan een argument met de modus voor het doorgeven van waarden echter worden omgezet in een argument met de invoerdoorgiftemodus.

Het doorgeven van een vluchtig veld (§15.5.4) als invoer-, uitvoer- of verwijzingsparameter veroorzaakt een waarschuwing, omdat het veld mogelijk niet als vluchtig wordt behandeld door de aangeroepen methode.

12.6.2.2 Corresponderende parameters

Voor elk argument in een argumentenlijst moet er een bijbehorende parameter zijn in het functielid of de gedelegeerde die wordt aangeroepen.

De parameterlijst die in het volgende wordt gebruikt, wordt als volgt bepaald:

  • Voor virtuele methoden en indexeringsfuncties die in klassen zijn gedefinieerd, wordt de parameterlijst gekozen uit de eerste declaratie of override van het functielid dat wordt gevonden door te beginnen met het statische type van de ontvanger en door te zoeken in de basisklassen.
  • Voor gedeeltelijke methoden wordt de parameterlijst van de declaratie voor gedeeltelijke methoden gebruikt.
  • Voor alle andere functieleden en gemachtigden is er slechts één lijst met parameters. Dit is de lijst die wordt gebruikt.

De positie van een argument of parameter wordt gedefinieerd als het aantal argumenten of parameters dat eraan voorafgaat in de argumentenlijst of parameterlijst.

De bijbehorende parameters voor argumenten voor functieleden worden als volgt ingesteld:

  • Argumenten in het argument_list van exemplaarconstructors, methoden, indexers en delegeren:
    • Een positioneel argument waarbij een parameter zich op dezelfde positie in de parameterlijst bevindt, komt overeen met die parameter, tenzij de parameter een parametermatrix is en het functielid wordt aangeroepen in de uitgevouwen vorm.
    • Een positioneel argument van een functielid met een parametermatrix die wordt aangeroepen in de uitgevouwen vorm, die plaatsvindt op of na de positie van de parametermatrix in de parameterlijst, komt overeen met een element in de parametermatrix.
    • Een benoemd argument komt overeen met de parameter van dezelfde naam in de parameterlijst.
    • Bij het aanroepen van de settoegangsfunctie komt de expressie die is opgegeven als de rechteroperand van de toewijzingsoperator, overeen met de impliciete value parameter van de declaratie van de settoegangsfunctie.
  • Bij het aanroepen van de get-accessor zijn er geen argumenten voor eigenschappen. Wanneer u de settoegangsfunctie aanroept, komt de expressie die is opgegeven als de rechteroperand van de toewijzingsoperator overeen met de impliciete waardeparameter van de declaratie van de settoegangsfunctie.
  • Voor door de gebruiker gedefinieerde unaire operators (inclusief conversies) komt de enkele operand overeen met de enkele parameter van de operatordeclaratie.
  • Voor door de gebruiker gedefinieerde binaire operatoren komt de linkeroperand overeen met de eerste parameter en komt de rechteroperand overeen met de tweede parameter van de operatordeclaratie.
  • Een niet-benoemd argument komt overeen met geen parameter wanneer deze zich achter een argument met een buiten positie bevindt of een benoemd argument dat overeenkomt met een parametermatrix.

    Opmerking: hiermee voorkomt u dat void M(bool a = true, bool b = true, bool c = true); wordt aangeroepen door M(c: false, valueB);. Het eerste argument wordt buiten positie gebruikt (het argument wordt gebruikt op de eerste positie, maar de parameter met de naam c zich op de derde positie bevindt), dus de volgende argumenten moeten worden benoemd. Met andere woorden, niet-gevolgde benoemde argumenten zijn alleen toegestaan wanneer de naam en de positie resulteren in het vinden van dezelfde corresponderende parameter. eindnotitie

12.6.2.3 Runtime-evaluatie van argumentlijsten

Tijdens de uitvoering van de aanroep van een functielid (§12.6.6), worden de expressies of variabele verwijzingen van een lijst met argumenten als volgt geëvalueerd, van links naar rechts:

  • Als voor een waardeargument de doorgangsmodus van de parameter een waarde is

    • de argumentexpressie wordt geëvalueerd en er wordt een impliciete conversie (§10.2) uitgevoerd op het bijbehorende parametertype. De resulterende waarde wordt de initiële waarde van de waardeparameter in de aanroep van het functielid.

    • anders is de passeermodus van de parameter invoer. Als het argument een variabele verwijzing is en er een identiteitsconversie bestaat (§10.2.2) tussen het type van het argument en het type van de parameter, wordt de resulterende opslaglocatie de opslaglocatie die wordt vertegenwoordigd door de parameter in de aanroep van het functielid. Anders wordt er een opslaglocatie gemaakt met hetzelfde type als die van de bijbehorende parameter. De argumentexpressie wordt geëvalueerd en er wordt een impliciete conversie (§10.2) uitgevoerd naar het bijbehorende parametertype. De resulterende waarde wordt opgeslagen in die opslaglocatie. Deze opslaglocatie wordt vertegenwoordigd door de invoerparameter in de aanroep van het functielid.

      Voorbeeld: Gegeven de volgende declaraties en methodeaanroepen:

      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
      

      In de M1(i) methodeaanroep wordt i zelf doorgegeven als invoerargument, omdat deze is geclassificeerd als een variabele en hetzelfde type heeft int als de invoerparameter. In de M1(i + 5) methodeaanroep wordt een niet-benoemde int variabele gemaakt, geïnitialiseerd met de waarde van het argument en vervolgens doorgegeven als invoerargument. Zie §12.6.4.2 en §12.6.4.4.

      einde voorbeeld

  • Voor een invoer-, uitvoer- of verwijzingsargument wordt de variabelereferentie geëvalueerd en wordt de resulterende opslaglocatie de opslaglocatie die wordt vertegenwoordigd door de parameter in de aanroep van het functielid. Voor een invoer- of verwijzingsargument wordt de variabele zeker toegewezen aan het punt van de methodeaanroep. Als de variabeleverwijzing wordt opgegeven als een uitvoerargument of een matrixelement van een reference_typeis, wordt er een runtimecontrole uitgevoerd om ervoor te zorgen dat het elementtype van de matrix identiek is aan het type van de parameter. Als deze controle mislukt, wordt er een System.ArrayTypeMismatchException gegenereerd.

Opmerking: deze runtimecontrole is vereist vanwege de covariantie van de matrix (§17,6). eindnotitie

Voorbeeld: In de volgende code

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

de tweede aanroep van F zorgt ervoor dat een System.ArrayTypeMismatchException wordt gegenereerd omdat het werkelijke elementtype van bstring is en niet object.

einde voorbeeld

Methoden, indexeerfuncties en instantieconstructors kunnen de meest rechtse parameter declareren als een parametermatrix (§15.6.2.4). Dergelijke functieleden worden aangeroepen in hun normale vorm of in hun uitgebreide vorm, afhankelijk van wat van toepassing is (§12.6.4.2):

  • Wanneer een functielid met een parametermatrix wordt aangeroepen in de normale vorm, is het argument voor de parametermatrix een enkele expressie die impliciet kan worden omgezet (§10.2) voor het parametermatrixtype. In dit geval fungeert de parametermatrix precies als een waardeparameter.
  • Wanneer een functielid met een parametermatrix wordt aangeroepen in de uitgevouwen vorm, geeft de aanroep nul of meer positionele argumenten op voor de parametermatrix, waarbij elk argument een expressie is die impliciet kan worden omgezet (§10.2) aan het elementtype van de parametermatrix. In dit geval maakt de aanroep een exemplaar van het parametertabeltype met een lengte die overeenkomt met het aantal argumenten, initialiseert de elementen van de tabelinstantie met de opgegeven argumentwaarden en wordt de zojuist aangemaakte tabelinstantie gebruikt als het werkelijke argument.

De expressies van een lijst met argumenten worden altijd in tekstvolgorde geëvalueerd.

Voorbeeld: Daarom het voorbeeld

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

produceert de uitvoer

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

einde voorbeeld

Wanneer een functielid met een parametermatrix wordt aangeroepen in de uitgevouwen vorm met ten minste één uitgevouwen argument, wordt de aanroep verwerkt alsof een expressie voor het maken van een matrix met een matrixinitiizer (§12.8.17.5) rond de uitgebreide argumenten is ingevoegd. Er wordt een lege matrix doorgegeven wanneer er geen argumenten zijn voor de parametermatrix; het is niet opgegeven of de verwijzing die is doorgegeven aan een nieuw toegewezen of bestaande lege matrix is.

Voorbeeld: Gegeven de declaratie

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

de volgende aanroepen van de uitgebreide vorm van de methode

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

precies overeenkomen met

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

einde voorbeeld

Wanneer argumenten worden weggelaten van een functielid met bijbehorende optionele parameters, worden de standaardargumenten van de declaratie van het functielid impliciet doorgegeven. (Dit kan betrekking hebben op het maken van een opslaglocatie, zoals hierboven beschreven.)

Opmerking: omdat deze altijd constant zijn, heeft de evaluatie geen invloed op de evaluatie van de resterende argumenten. eindnotitie

12.6.3 Type inferentie

12.6.3.1 Algemeen

Wanneer een algemene methode wordt aangeroepen zonder typeargumenten op te geven, probeert een typedeductie proces argumenten voor de aanroep af te stellen. Door de aanwezigheid van typedeductie kan een handigere syntaxis worden gebruikt voor het aanroepen van een algemene methode en kan de programmeur voorkomen dat redundante typegegevens worden opgegeven.

voorbeeld van:

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

Door middel van typedeductie worden de typeargumenten int en string bepaald van de argumenten tot de methode.

einde voorbeeld

Typedeductie vindt plaats als onderdeel van de bindingstijdverwerking van een methode-aanroep (§12.8.10.2) en vindt plaats vóór de overbelastingsresolutiestap van de aanroep. Wanneer een bepaalde methodegroep wordt opgegeven in een methode-aanroep en er geen typeargumenten worden opgegeven als onderdeel van de aanroep van de methode, wordt typedeductie toegepast op elke algemene methode in de methodegroep. Als de type-inferentie slaagt, worden de afgeleide typeargumenten gebruikt om de typen van argumenten voor de volgende overbelastingsoplossing te bepalen. Als overbelastingsresolutie een algemene methode kiest als de methode die moet worden aangeroepen, worden de uitgestelde typeargumenten gebruikt als de typeargumenten voor de aanroep. Als typedeductie voor een bepaalde methode mislukt, neemt die methode niet deel aan overbelastingsresolutie. De fout van typedeductie, op en van zichzelf, veroorzaakt geen bindingstijdfout. Het leidt echter vaak tot een bindingstijdfout wanneer overbelastingsoplossing geen toepasselijke methoden kan vinden.

Als elk opgegeven argument niet overeenkomt met precies één parameter in de methode (§12.6.2.2), of als er een niet-optionele parameter zonder corresponderend argument is, mislukt deductie onmiddellijk. Anders wordt ervan uitgegaan dat de algemene methode de volgende handtekening heeft:

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

Met een methodeoproep van het formulier M(E₁ ...Eₓ) de taak van typedeductie is het vinden van unieke typeargumenten S₁...Sᵥ voor elk van de typeparameters X₁...Xᵥ, zodat de aanroep M<S₁...Sᵥ>(E₁...Eₓ) geldig wordt.

Het proces van typedeductie wordt hieronder beschreven als een algoritme. Een conforme compiler kan worden geïmplementeerd met behulp van een alternatieve benadering, mits deze in alle gevallen hetzelfde resultaat bereikt.

Tijdens het proces van inferentie wordt elke typeparameter Xᵢ ofwel vastgelegd op een bepaald type Sᵢ, ofwel niet-vastgelegd met een bijbehorende set van beperkingen. Elk van de beperkingen is een bepaald type T. In eerste instantie is elke typevariabele Xᵢ niet vastgelegd en heeft geen grenzen.

Typedeductie vindt plaats in fasen. Elke fase probeert argumenten voor meer typevariabelen af te stellen op basis van de bevindingen van de vorige fase. In de eerste fase worden enkele initiële afleidingen van grenzen gemaakt, terwijl in de tweede fase typevariabelen worden vastgelegd op specifieke typen en meer grenzen worden afgeleid. De tweede fase moet mogelijk een aantal keren worden herhaald.

Opmerking: Type deductie wordt ook gebruikt in andere contexten, waaronder voor het converteren van methodegroepen (§12.6.3.14) en het vinden van het beste gemeenschappelijke type expressies (§12.6.3.15). eindnotitie

12.6.3.2 De eerste fase

Voor elk van de methodeargumenten Eᵢ:

  • Als Eᵢ een anonieme functie is, wordt een expliciete parametertypedeductie (§12.6.3.8) gemaakt vanEᵢtotTᵢ
  • Als Eᵢ een type U heeft en de bijbehorende parameter een waardeparameter (§15.6.2.2) is, wordt een ondergrensafleiding (§12.6.3.10) gemaakt vanUtotTᵢ.
  • Anders, als Eᵢ een type U heeft en de bijbehorende parameter een referentieparameter is (§15.6.2.3.3) of uitvoerparameter (§15.6.6.3 2.3.4) wordt vervolgens een exacte deductie (§12.6.3.9) gemaakt vanUtotTᵢ.
  • Als Eᵢ een type U heeft en de bijbehorende parameter een invoerparameter is (§15.6.2.3.2) en Eᵢ een invoerargument is, wordt een exacte deductie (§12.6.3.9) gemaakt vanUtotTᵢ.
  • Als Eᵢ een type U heeft en de bijbehorende parameter een invoerparameter is (§15.6.2.3.2) wordt een ondergrensdeductie (§12.6.3.10) gemaakt vanUtotTᵢ.
  • Anders wordt er geen deductie gemaakt voor dit argument.

12.6.3.3 De tweede fase

De tweede fase gaat als volgt:

  • Alle niet-gefixeerde variabelen typevariabelen Xᵢ die niet afhankelijk zijn van (§12.6.3.6) alle Xₑ zijn vast (§12.6.3.12).
  • Als er geen dergelijke typevariabelen bestaan, worden alle niet-vaste typevariabelen Xᵢvastgezet waarvoor alle volgende voorwaarden gelden:
    • Er is ten minste één typevariabele Xₑ die afhankelijk is vanXᵢ
    • Xᵢ heeft een niet-lege set grenzen
  • Als er geen dergelijke typevariabelen bestaan en er nog steeds niet-vastgefixeerde typevariabelen zijn, mislukt deductie.
  • Anders, als er verder geen niet-opgeloste typevariabelen bestaan, dan slaagt de type-inferentie.
  • Anders geldt dat voor alle argumenten Eᵢ met het bijbehorende parametertype Tᵢ waarbij de uitvoertypen (§12.6.3.5) niet-opgeloste variabelen typevariabelen Xₑ bevatten, maar de invoertypen (§12.6.3.4) niet, een uitvoertypedeductie (§12.6.3.7) wordt gemaakt vanEᵢtotTᵢ. Vervolgens wordt de tweede fase herhaald.

12.6.3.4 Invoertypen

Als E een methodegroep of impliciet getypte anonieme functie is en T een gedelegeerdetype of expressieboomtype is, dan zijn alle parametertypen van Tinvoertypen vanEmet het typeT.

12.6.3.5 Uitvoertypen

Als E een methodegroep of een anonieme functie is en T een type gedelegeerde of expressiestructuur is, is het retourtype van T een uitvoertype vanEmet het typeT.

12.6.3.6 Afhankelijkheid

Een niet-opgeloste typevariabele Xᵢis rechtstreeks afhankelijk van een niet-opgeloste variabele typevariabele Xₑ als voor een bepaald argument Eᵥ type TᵥXₑ voorkomt in een invoertype van Eᵥ met het type Tᵥ en Xᵢ optreedt in een uitvoertype van Eᵥ met het type Tᵥ.

Xₑ is afhankelijk vanXᵢ als Xₑrechtstreeks afhankelijk is vanXᵢ of dat Xᵢrechtstreeks afhankelijk is vanXᵥ en Xᵥafhankelijk is vanXₑ. Dus "hangt af van" is de transitieve maar niet reflexieve sluiting van "hangt rechtstreeks af van".

12.6.3.7 Uitvoertypededucties

Een uitvoertypedeductie wordt gemaakt van een expressie Eom een type T op de volgende manier te:

  • Als E een anonieme functie is met afgeleid retourtype U (§12.6.3.13) en T een gedelegeerd type of expressieboomtype is met retourtype Tₓ, dan wordt een ondergrensinference (§12.6.3.10) gemaakt vanUtotTₓ.
  • Als E een methodegroep is en T een gedelegeerd type of expressiebombomitetype is met parametertypen T₁...Tᵥ en retourtype Tₓ, en overbelastingsresolutie van E met de typen T₁...Tᵥ een enkele methode oplevert met retourtype U, dan wordt een inferentie van de ondergrens gemaakt vanUtotTₓ.
  • Als E een expressie is met het type U, dan wordt er een ondergrens deductie gemaakt vanUnaarT.
  • Anders worden er geen deducties gemaakt.

12.6.3.8 Expliciete parametertypeafleidingen

Een expliciete parametertypedeductie wordt gemaakt van een expressie E:

  • Als E een expliciet getypte anonieme functie is met parametertypen U₁...Uᵥ en T een type gedelegeerde of expressiestructuur is met parametertypen V₁...Vᵥ wordt voor elke Uᵢ een exacte deductie (§12.6.3.9) gemaakt vanUᵢtot de bijbehorende Vᵢ.

12.6.3.9 Exacte deducties

Een exacte afleidingvan een type Utot een type V wordt als volgt gemaakt:

  • Als V een van de niet-vastgesteldeXᵢ is, wordt U toegevoegd aan de set van exacte limieten voor Xᵢ.
  • Anders worden sets V₁...Vₑ en U₁...Uₑ bepaald door te controleren of een van de volgende gevallen van toepassing is:
    • V is een matrixtype V₁[...] en U is een matrixtype U₁[...] van dezelfde rang
    • V is het type V₁? en U het type U₁
    • V is een samengesteld type C<V₁...Vₑ> en U een samengesteld type C<U₁...Uₑ>
      Als een van deze gevallen van toepassing is, wordt een exacte deductie gemaakt van elke Uᵢ naar de bijbehorende Vᵢ.
  • Anders worden er geen deducties gemaakt.

12.6.3.10 Ondergrensdeducties

Een ondergrensdeductie van een type U:

  • Als V een van de niet-vastgesteldeXᵢ is, wordt U toegevoegd aan de set van ondergrenzen voor Xᵢ.
  • Als V het type V₁? is en U is het type U₁?, wordt er een ondergrensinferentie gemaakt van U₁ tot V₁.
  • Anders worden sets U₁...Uₑ en V₁...Vₑ bepaald door te controleren of een van de volgende gevallen van toepassing is:
    • V is een matrixtype V₁[...]en U is een matrixtype U₁[...]van dezelfde rang
    • V is een van IEnumerable<V₁>, ICollection<V₁>, IReadOnlyList<V₁>>, IReadOnlyCollection<V₁> of IList<V₁> en U een enkeldimensionaal matrixtype U₁[]
    • V is een samengesteld class, struct, interface of delegate type C<V₁...Vₑ> en er is een uniek type C<U₁...Uₑ> dat U (of, als U een type parameteris, de effectieve basisklasse of een lid van de effectieve interfaceset) identiek is aan, inherits van (direct of indirect) of implementeert (direct of indirect) C<U₁...Uₑ>.
    • (De beperking 'uniekheid' betekent dat in het geval van interface C<T>{} class U: C<X>, C<Y>{}er geen deductie wordt gemaakt bij het afleiden van U tot C<T>, omdat U₁X of Ykan zijn.)
      Als een van deze gevallen van toepassing is, wordt van elke Uᵢ een deductie gemaakt op de bijbehorende Vᵢ als volgt:
    • Als Uᵢ geen referentietype is, wordt er een exacte inferentie gemaakt
    • Anders, als U een arraytype is, wordt er een ondergrensinferentie gemaakt
    • Als VC<V₁...Vₑ> is, dan is deductie afhankelijk van de i-th typeparameter van C:
      • Als deze covariant is, wordt er een ondergrensinferentie gemaakt.
      • Als het contravariant is, wordt er een bovengrensdeductie gemaakt.
      • Als deze invariant is, wordt er een exacte deductie gemaakt.
  • Anders worden er geen deducties gemaakt.

12.6.3.11 Bovengrensdeducties

Een bovengrensdeductie van een type U:

  • Als V een van de niet-vastgesteldeXᵢ is, wordt U toegevoegd aan de set bovengrens voor Xᵢ.
  • Anders worden sets V₁...Vₑ en U₁...Uₑ bepaald door te controleren of een van de volgende gevallen van toepassing is:
    • U is een matrixtype U₁[...]en V is een matrixtype V₁[...]van dezelfde rang
    • U is een van IEnumerable<Uₑ>, ICollection<Uₑ>, IReadOnlyList<Uₑ>, IReadOnlyCollection<Uₑ> of IList<Uₑ> en V een enkeldimensionaal matrixtype Vₑ[]
    • U is het type U1? en V het type V1?
    • U is het type klasse, struct, interface of gemachtigde C<U₁...Uₑ> en V is een class, struct, interface of delegate type dat identical is, inherits van (direct of indirect) of implementeert (direct of indirect) een uniek type C<V₁...Vₑ>
    • (De 'uniekheidseis' betekent dat gezien een interface C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}er geen afleiding wordt gemaakt van C<U₁> naar V<Q>. Afleidingen worden niet gedaan van U₁ naar X<Q> of naar Y<Q>.)
      Als een van deze gevallen van toepassing is, wordt van elke Uᵢ een deductie gemaakt op de bijbehorende Vᵢ als volgt:
    • Als Uᵢ geen referentietype is, wordt er een exacte inferentie gemaakt
    • Als V een matrixtype is, wordt er anders een bovengrensafleiding gemaakt.
    • Als UC<U₁...Uₑ> is, dan is deductie afhankelijk van de i-th typeparameter van C:
      • Als het covariant is, wordt er een bovengrensdeductie gemaakt.
      • Als het contravariant is, wordt er een ondergrensdeductie gemaakt.
      • Als deze invariant is, wordt er een exacte deductie gemaakt.
  • Anders worden er geen deducties gemaakt.

12.6.3.12 Repareren

Een niet-opgeloste typevariabele Xᵢ met een set grenzen is als volgt vaste:

  • De set kandidaattypenUₑ begint als de set van alle typen in de set van grenzen voor Xᵢ.
  • Elke grens voor Xᵢ wordt op zijn beurt onderzocht: voor elke exacte grens van Xᵢ alle typen Uₑ die niet identiek zijn aan U uit de kandidaatset worden verwijderd. Voor elke ondergrens U van Xᵢ worden alle typen Uₑ waarvoor er geen impliciete conversie van U is, uit de kandidaatset verwijderd. Voor elke bovengrens van Xᵢ alle typen Uₑ waaruit geen impliciete conversie naar U wordt verwijderd uit de kandidaatset.
  • Als er onder de overgebleven kandidaattypen Uₑ een uniek type V is waarop een impliciete conversie van alle andere kandidaattypen mogelijk is, wordt Xᵢ vastgezet op V.
  • Anders faalt de typeafleiding.

12.6.3.13 Afgeleid retourtype

Het uitgestelde retourtype van een anonieme functie F wordt gebruikt tijdens typedeductie en overbelastingsresolutie. Het afgeleide retourtype kan alleen worden bepaald voor een anonieme functie waarbij alle parametertypen bekend zijn, omdat ze expliciet worden opgegeven, via een anonieme functieconversie of afgeleid tijdens typedeductie bij een omsluitende generieke methodeaanroep.

De afgeleide effectieve retourtype wordt als volgt bepaald:

  • Als de hoofdtekst van F een uitdrukking is die een type heeft, dan is het afgeleide effectieve retourtype van F het type van die uitdrukking.
  • Als het lichaam van F een blok is en de verzameling expressies in de return instructies van het blok een meest gangbaar type T heeft (§12.6.3.15), dan is het afgeleide effectieve retourtype van FT.
  • Anders kan een effectief retourtype niet worden afgeleid voor F.

Het afgeleide retourtype wordt als volgt bepaald:

  • Als F asynchroon is en de hoofdtekst van F een expressie is die als niets is geclassificeerd (§12.2), of een blok waarin geen return instructies expressies hebben, is het uitgestelde retourtype «TaskType» (§15.15.1).
  • Als F asynchroon is en een uitgestelde effectieve retourtype Theeft, is het uitgestelde retourtype «TaskType»<T>»(§15.15.1).
  • Als F niet-asynchroon is en een afgeleid effectief retourtype Theeft, is het afgeleide retourtype T.
  • Anders kan een retourtype niet worden afgeleid voor F.

voorbeeld: als voorbeeld van typedeductie waarbij anonieme functies betrokken zijn, kunt u de Select extensiemethode overwegen die is gedeclareerd in de klasse System.Linq.Enumerable:

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

Ervan uitgaande dat de System.Linq naamruimte is geïmporteerd met een using namespace instructie, en gegeven een klasse Customer met een Name-eigenschap van type string, kan de Select-methode worden gebruikt om de namen van een lijst met klanten te selecteren:

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

De aanroep van de extensiemethode (§12.8.10.3) van Select wordt verwerkt door de aanroep te herschrijven naar een statische methode-aanroep:

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

Omdat typeargumenten niet expliciet zijn opgegeven, wordt typedeductie gebruikt om de typeargumenten af te stellen. Ten eerste is het argument van de klant gerelateerd aan de bronparameter, waardoor TSource moet worden Customer. Vervolgens wordt c met behulp van het hierboven beschreven deductieproces van het anonieme functietype het type Customergegeven, en wordt de expressie c.Name gerelateerd aan het retourtype van de selectieparameter, waarbij TResult wordt afgeleid als string. De aanroep is dus gelijk aan

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

en het resultaat is van het type IEnumerable<string>.

In het volgende voorbeeld ziet u hoe deductie van anonieme functietypen het mogelijk maakt om typegegevens te 'stromen' tussen argumenten in een algemene methode-aanroep. Gegeven de volgende methode en aanroeping:

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

typedeductie voor de aanroep gaat als volgt: Eerst is het argument "1:15:30" gerelateerd aan de waardeparameter, waardoor X moet worden string. Vervolgens krijgt de parameter van de eerste anonieme functie, s, het afgeleide type stringen is de expressie TimeSpan.Parse(s) gerelateerd aan het retourtype van f1, waardoor Y wordt geïnterpreteerd als System.TimeSpan. Ten slotte krijgt de parameter van de tweede anonieme functie, t, het uitgestelde type System.TimeSpanen de expressie t.TotalHours is gerelateerd aan het retourtype van f2, waardoor Z moet worden double. Het resultaat van de aanroep is dus van het type double.

einde voorbeeld

12.6.3.14 Typedeductie voor conversie van methodegroepen

Net als bij aanroepen van algemene methoden wordt typedeductie ook toegepast wanneer een methodegroep M met een algemene methode wordt geconverteerd naar een bepaald type gedelegeerde D (§10,8). Gegeven van een methode

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

en de methodegroep M wordt toegewezen aan het delegeertype D, is de taak van type-inferentie om typeargumenten te vinden S₁...Sᵥ, zodat de expressie:

M<S₁...Sᵥ>

wordt compatibel (§20.2) met D.

In tegenstelling tot het algoritme voor typedeductie voor algemene methode-aanroepen, zijn er in dit geval alleen argumenten typen, geen argument expressies. Er zijn met name geen anonieme functies en dus geen noodzaak voor meerdere fasen van deductie.

In plaats daarvan worden alle Xᵢ beschouwd als niet-vastgemaakteen wordt een ondergrensdeductie gemaakt van elk argumenttype Uₑ van D. Als er voor geen van de Xᵢ limieten werden gevonden, mislukt type-inferentie. Anders worden alle Xᵢvaste aan overeenkomende Sᵢ, wat het resultaat is van typedeductie.

12.6.3.15 Het vinden van het beste algemene type van een set expressies

In sommige gevallen moet een gemeenschappelijk type worden afgeleid voor een set expressies. In het bijzonder worden de elementtypen van impliciet getypte matrices en de retourtypen van anonieme functies met blok lichamen op deze manier gevonden.

Het meest voorkomende type voor een set expressies E₁...Eᵥ wordt als volgt bepaald:

  • Er wordt een nieuwe niet-vast typevariabele geïntroduceerd X.
  • Voor elke expressie Ei een uitvoertypedeductie (§12.6.3.7) wordt van daaruit uitgevoerd naar X.
  • X is vaste (§12.6.3.12), en het resulterende type is het meest gangbare type.
  • Anders mislukt inferentie.

Opmerking: Deze inferentie is intuïtief gelijk aan het aanroepen van een methode void M<X>(X x₁ ... X xᵥ) met de Eᵢ als argumenten en het afleiden van X. eindnotitie

12.6.4 Overbelastingsresolutie

12.6.4.1 Algemeen

Overbelastingsresolutie is een mechanisme voor bindingstijd voor het selecteren van het beste functielid om een lijst met argumenten en een set kandidaatfunctieleden aan te roepen. Overbelastingsresolutie selecteert het functielid dat moet worden aangeroepen in de volgende afzonderlijke contexten binnen C#:

  • Aanroep van een methode die is genoemd in een invocation_expression (§12.8.10).
  • Aanroep van een instantieconstructor met de naam in een object_creation_expression (§12.8.17.2).
  • Aanroep van een toegangsfunctie voor een indexeerfunctie via een element_access (§12.8.12).
  • Aanroepen van een vooraf gedefinieerde of door de gebruiker gedefinieerde operator waarnaar wordt verwezen in een expressie (§12.4.4 en §12.4.5).

Elk van deze contexten definieert de set kandidaatfunctieleden en de lijst met argumenten op een eigen unieke manier. De set kandidaten voor een methode-aanroep bevat bijvoorbeeld geen methoden die zijn gemarkeerd als override (§12.5), en methoden in een basisklasse zijn geen kandidaten als een methode in een afgeleide klasse van toepassing is (§12.8.10.2).

Zodra de leden van de kandidaatfunctie en de argumentenlijst zijn geïdentificeerd, is de selectie van het beste functielid in alle gevallen hetzelfde:

  • Ten eerste wordt de set kandidaatfunctieleden gereduceerd tot die functieleden die van toepassing zijn op de opgegeven argumentenlijst (§12.6.4.2). Als deze gereduceerde set leeg is, treedt er een compilatietijdfout op.
  • Vervolgens bevindt het beste functielid uit de set van toepasselijke kandidaatfunctieleden zich. Als de set slechts één functielid bevat, is dat functielid het beste functielid. Anders is het beste functielid het ene functielid dat beter is dan alle andere functieleden met betrekking tot de opgegeven argumentenlijst, mits elk functielid wordt vergeleken met alle andere functieleden die gebruikmaken van de regels in §12.6.4.3. Als er niet precies één functielid is dat beter is dan alle andere functieleden, is de aanroep van het functielid niet eenduidig en treedt er een bindingstijdfout op.

De volgende subclauses definiëren de exacte betekenis van de termen toepasselijke functielid en beter functielid.

12.6.4.2 Toepasselijk functielid

Een functielid wordt geacht een van toepassing functielid te zijn met betrekking tot een lijst met argumenten A wanneer alle volgende waar zijn:

  • Elk argument in A komt overeen met een parameter in de declaratie van het functielid, zoals beschreven in §12.6.2.2, ten hoogste één argument komt overeen met elke parameter en een parameter waarvoor geen argument overeenkomt, is een optionele parameter.
  • Voor elk argument in Ais de parameterdoorgiftemodus van het argument identiek aan de parameterdoorgiftemodus van de bijbehorende parameter en
    • voor een waardeparameter of een parametermatrix bestaat een impliciete conversie (§10.2) van de argumentexpressie tot het type van de bijbehorende parameter, of
    • voor een verwijzings- of uitvoerparameter is er een identiteitsconversie tussen het type argumentexpressie (indien aanwezig) en het type van de bijbehorende parameter, of
    • voor een invoerparameter wanneer het bijbehorende argument de wijzigingsfunctie in heeft, is er een identiteitsconversie tussen het type van de argumentexpressie (indien van toepassing) en het type van de bijbehorende parameter, of
    • voor een invoerparameter wanneer het bijbehorende argument de in modifier weglaat, bestaat er een impliciete conversie (§10.2) van de argumentexpressie tot het type van de bijbehorende parameter.

Voor een functielid dat een parametermatrix bevat, als het functielid van toepassing is volgens de bovenstaande regels, wordt gezegd dat het van toepassing is in de normale vorm. Als een functielid dat een parametermatrix bevat, niet van toepassing is in de normale vorm, kan het functielid in plaats daarvan van toepassing zijn in de uitgevouwen vorm:

  • Het uitgevouwen formulier wordt samengesteld door de parametermatrix in de functieliddeclaratie te vervangen door nul of meer waardeparameters van het elementtype van de parametermatrix, zodat het aantal argumenten in de argumentenlijst A overeenkomt met het totale aantal parameters. Als A minder argumenten heeft dan het aantal vaste parameters in de declaratie van het functielid, kan de uitgevouwen vorm van het functielid niet worden samengesteld en is dus niet van toepassing.
  • Anders is het uitgevouwen formulier van toepassing als voor elk argument in Aeen van de volgende waar is:
    • de parameterdoorgiftemodus van het argument is identiek aan de parameterdoorgiftemodus van de bijbehorende parameter en:
      • voor een parameter met vaste waarde of een waardeparameter die door de uitbreiding is gemaakt, bestaat er een impliciete conversie (§10.2) van de argumentexpressie tot het type van de bijbehorende parameter; of
      • voor een by-reference-parameter is het type argumentexpressie identiek aan het type van de bijbehorende parameter.
    • de parameterdoorgiftemodus van het argument is waarde en de parameterdoorgiftemodus van de bijbehorende parameter is invoer en een impliciete conversie (§10.2) bestaat uit de argumentexpressie tot het type van de bijbehorende parameter.

Wanneer de impliciete conversie van het argumenttype naar het parametertype van een invoerparameter een dynamische impliciete conversie is (§10.2.10), zijn de resultaten niet gedefinieerd.

Voorbeeld: Gegeven de volgende declaraties en methodeaanroepen:

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
}

einde voorbeeld

  • Een statische methode is alleen van toepassing als de methodegroep het resultaat is van een simple_name of een member_access via een type.
  • Een instantiemethode is alleen van toepassing als de methodegroep het resultaat is van een simple_name, een member_access via een variabele of waarde of een base_access.
    • Als de methodegroep het resultaat is van een simple_name, is een instantiemethode alleen van toepassing als this toegang is toegestaan §12.8.14.
  • Wanneer de methodegroep het resultaat is van een member_access die kan worden gebruikt via een exemplaar of een type zoals beschreven in §12.8.7.2, zijn zowel exemplaar- als statische methoden van toepassing.
  • Een algemene methode waarvan de typeargumenten (expliciet opgegeven of afgeleid) niet aan hun beperkingen voldoen, is niet van toepassing.
  • In de context van een methodegroepconversie bestaat er een identiteitsconversie (§10.2.2) of een impliciete verwijzingsconversie (§10.2.8) van het retourtype van de methode naar het retourtype van de gemachtigde. Anders is de kandidaatmethode niet van toepassing.

12.6.4.3 Beter functielid

Voor het bepalen van het betere functielid wordt een lijst met gestreepte argumenten A samengesteld die alleen de argumentexpressies zelf bevat in de volgorde waarin ze worden weergegeven in de oorspronkelijke lijst met argumenten en eventuele out of ref argumenten weglaat.

Parameterslijsten voor elk van de kandidaatfunctieleden worden op de volgende manier samengesteld:

  • Het uitgevouwen formulier wordt gebruikt als het functielid alleen van toepassing was in het uitgevouwen formulier.
  • Optionele parameters zonder bijbehorende argumenten worden verwijderd uit de lijst met parameters
  • Verwijzings- en uitvoerparameters worden verwijderd uit de lijst met parameters
  • De parameters worden opnieuw gerangschikt, zodat ze zich op dezelfde positie bevinden als het bijbehorende argument in de lijst met argumenten.

Gezien een lijst met argumenten A met een set argumentexpressies {E₁, E₂, ..., Eᵥ} en twee toepasselijke functieleden Mᵥ en Mₓ met parametertypen {P₁, P₂, ..., Pᵥ} en {Q₁, Q₂, ..., Qᵥ}, wordt Mᵥ gedefinieerd als een beter functielid dan Mₓ als

  • voor elk argument is de impliciete conversie van Eᵥ naar Qᵥ niet beter dan de impliciete conversie van Eᵥ naar Pᵥ, en
  • voor ten minste één argument is de conversie van Eᵥ naar Pᵥ beter dan de conversie van Eᵥ naar Qᵥ.

Als de parametertypereeksen {P₁, P₂, ..., Pᵥ} en {Q₁, Q₂, ..., Qᵥ} gelijkwaardig zijn (dat wil bijvoorbeeld dat elke Pᵢ een identiteitsconversie heeft naar de bijbehorende Qᵢ), worden de volgende regels voor tie-breaking toegepast om het betere lid van de functie te bepalen.

  • Als Mᵢ een niet-generieke methode is en Mₑ een algemene methode is, is Mᵢ beter dan Mₑ.
  • Als Mᵢ in de normale vorm van toepassing is en Mₑ een parametermatrix heeft en alleen van toepassing is in de uitgevouwen vorm, is Mᵢ beter dan Mₑ.
  • Als beide methoden parametermatrices hebben en alleen van toepassing zijn in hun uitgevouwen vormen en als de parametermatrix van Mᵢ minder elementen heeft dan de parametermatrix van Mₑ, is Mᵢ beter dan Mₑ.
  • Als Mᵥ meer specifieke parametertypen heeft dan Mₓ, is Mᵥ beter dan Mₓ. Laat {R1, R2, ..., Rn} en {S1, S2, ..., Sn} de niet-geïnstantieerde en niet-uitgevouwen parametertypen van Mᵥ en Mₓvoorstellen. Mᵥparametertypen zijn specifieker dan Mₓs als voor elke parameter Rx niet minder specifiek is dan Sx, en voor ten minste één parameter is Rx specifieker dan Sx:
    • Een typeparameter is minder specifiek dan een niet-typeparameter.
    • Recursief is een samengesteld type specifieker dan een ander samengesteld type (met hetzelfde aantal typeargumenten) als ten minste één typeargument specifieker is en geen typeargument minder specifiek is dan het bijbehorende typeargument in het andere.
    • Een matrixtype is specifieker dan een ander matrixtype (met hetzelfde aantal dimensies) als het elementtype van de eerste specifieker is dan het elementtype van de tweede.
  • Anders geldt dat als één lid een niet-gelifte operator is en de andere een gelifte operator, de niet-gelifte beter is.
  • Als geen van beide functieleden beter is gebleken en alle parameters van Mᵥ een corresponderend argument hebben, terwijl standaardargumenten moeten worden vervangen door ten minste één optionele parameter in Mₓ, is Mᵥ beter dan Mₓ.
  • Als voor ten minste één parameter Mᵥ de betere keuze voor het doorgeven van parameters gebruikt (§12.6.4.4) dan de bijbehorende parameter in Mₓ en geen van de parameters in Mₓ de betere keuze voor parameterdoorgifte dan Mᵥ, is Mᵥ beter dan Mₓ.
  • Anders is geen functielid beter.

12.6.4.4 Betere parameter-doorvoermethode

Het is toegestaan om overeenkomstige parameters in twee overbelaste methoden alleen te laten verschillen door de parameterdoorgiftemodus op voorwaarde dat een van de twee parameters de modus voor het doorgeven van waarden heeft, als volgt:

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

Gezien int i = 10;, volgens §12.6.4.2, leiden de aanroepen M1(i) en M1(i + 5) ertoe dat beide overbelastingen van toepassing zijn. In dergelijke gevallen is de methode met de parameter-passingsmodus van waarde de betere keuze voor de parameterdoorgiftemodus.

Opmerking: er is geen keuze nodig voor argumenten van invoer-, uitvoer- of verwijzingsmodi, omdat deze argumenten alleen overeenkomen met exact dezelfde parameterdoorgiftemodi. eindnotitie

12.6.4.5 Betere conversie van expressie

Gezien een impliciete conversie C₁ die wordt geconverteerd van een expressie E naar een type T₁en een impliciete conversie C₂ die converteert van een expressie E naar een type T₂, is C₁ een betere conversie dan C₂ als een van de volgende bewaringen geldt:

  • E exact overeenkomt met T₁ en E komt niet exact overeen met T₂ (§12.6.4.6)
  • E komt exact overeen met zowel T₁ als T₂, en T₁ is een beter conversiedoel dan T₂ (§12.6.4.7)
  • E is een methodegroep (§12.2), is T₁ compatibel (§20.4) met de ene beste methode uit de methodegroep voor conversie C₁en T₂ is niet compatibel met de enige beste methode uit de methodegroep voor conversie C₂

12.6.4.6 Exact overeenkomende expressie

Gezien een expressie E en een type T, komt Eexact overeen metT als een van de volgende waarden geldt:

  • E heeft een type Sen er bestaat een identiteitsconversie van S tot T
  • E is een anonieme functie, T is een delegaat type D of een expressieboomtype Expression<D> en geldt een van de volgende:
    • Er bestaat een afgeleid retourtype X voor E in de context van de parameterlijst van D (§12.6.3.12) en er bestaat een identiteitsconversie van X tot het retourtype D
    • E is een async lambda zonder retourwaarde, en D heeft een retourtype dat een niet-generieke «TaskType» is
    • E is niet-asynchroon en D heeft een retourtype Y, of E is asynchroon en D heeft een retourtype «TaskType»<Y>(§15.15.1), en een van de volgende voorwaarden:
      • De hoofdtekst van E is een expressie die exact overeenkomt met Y
      • De hoofdtekst van E is een blok waarin elke retourinstructie een expressie retourneert die exact overeenkomt met Y

12.6.4.7 Beter conversiedoelstelling

Voor de twee typen T₁ en T₂is T₁ een beter conversiedoel dan T₂ als een van de volgende voorwaarden geldt:

  • Er bestaat een impliciete conversie van T₁ naar T₂ en er bestaat geen impliciete conversie van T₂ naar T₁
  • T₁ is «TaskType»<S₁>(§15.15.1), T₂ is «TaskType»<S₂>en is S₁ een beter conversiedoel dan S₂
  • T₁ is «TaskType»<S₁>(§15.15.1), T₂ is «TaskType»<S₂>en T₁ is meer gespecialiseerd dan T₂
  • T₁ is S₁ of S₁? waarbij S₁ een ondertekend integraal type is en T₂ is S₂ of S₂? waarbij S₂ een niet-ondertekend integraaltype is. Specifiek:
    • S₁ is sbyte en S₂ is byte, ushort, uintof ulong
    • S₁ is short en S₂ is ushort, uintof ulong
    • S₁ is int en S₂ is uintofwel ulong
    • S₁ is long en S₂ is ulong

12.6.4.8 Overloading in algemene klassen

Opmerking: hoewel handtekeningen zoals aangegeven uniek zijn (§8.6), is het mogelijk dat vervanging van typeargumenten identieke handtekeningen oplevert. In een dergelijke situatie kiest overbelastingsoplossing de meest specifieke (§12.6.4.3) van de oorspronkelijke handtekeningen (vóór vervanging van typeargumenten), als deze bestaat en meldt anders een fout. eindnotitie

voorbeeld: in de volgende voorbeelden ziet u overbelastingen die geldig en ongeldig zijn volgens deze regel:

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

einde voorbeeld

12.6.5 Compileertijdcontrole van dynamische aanroep van leden

Hoewel overbelastingsresolutie van een dynamisch gebonden bewerking plaatsvindt tijdens runtime, is het soms mogelijk om tijdens het compileren de lijst met functieleden te kennen waaruit een overbelasting wordt gekozen:

  • Voor een delegatie-aanroep (§12.8.10.4) is de lijst een enkel functielid met dezelfde parameterlijst als die van de delegate_type van de aanroep.
  • Voor een methodeaanroep (§12.8.10.2) op een type of op een waarde waarvan het statische type niet dynamisch is, is de set toegankelijke methoden in de methodegroep bekend tijdens het compileren.
  • Voor een expressie voor het maken van objecten (§12.8.17.2) is de set toegankelijke constructors in het type bekend tijdens het compileren.
  • Voor toegang tot een indexeerfunctie (§12.8.12.3) is de set toegankelijke indexeerfuncties in de ontvanger bekend tijdens het compileren.

In deze gevallen wordt er een beperkte compilatietijdcontrole uitgevoerd op elk lid van de bekende set functieleden, om te zien of er met zekerheid kan worden vastgesteld dat deze nooit tijdens de uitvoeringstijd wordt aangeroepen. Voor elk functielid F een gewijzigde parameter en lijst met argumenten zijn samengesteld:

  • Als F een algemene methode en typeargumenten zijn opgegeven, worden deze eerst vervangen door de typeparameters in de parameterlijst. Als er echter geen typeargumenten zijn opgegeven, vindt er geen vervanging plaats.
  • Vervolgens wordt elke parameter waarvan het type is geopend (dat wil bijvoorbeeld een typeparameter bevatten; zie §8.4.3) wordt verwijderd, samen met de bijbehorende parameter(en).

Voor F om de controle te doorstaan, moeten alle volgende voorwaarden gelden:

  • De gewijzigde parameterlijst voor F is van toepassing op de lijst met gewijzigde argumenten in termen van §12.6.4.2.
  • Alle samengestelde typen in de gewijzigde parameterlijst voldoen aan hun beperkingen (§8.4.5).
  • Als de typeparameters van F in de bovenstaande stap zijn vervangen, worden aan hun beperkingen voldaan.
  • Als F een statische methode is, mag de methodegroep niet ontstaan uit een member_access waarvan tijdens het compileren bekend is dat de ontvanger een variabele of waarde is.
  • Als F een instantiemethode is, heeft de methodegroep niet geresulteerd uit een member_access waarvan de ontvanger op het moment van compileren bekend is dat het een type is.

Als er geen kandidaat aan deze test is geslaagd, treedt er een compilatiefout op.

12.6.6 Functielid aanroepen

12.6.6.1 Algemeen

In dit subclause wordt het proces beschreven dat tijdens de uitvoering plaatsvindt om een bepaald functielid aan te roepen. Er wordt vanuit gegaan dat een bindingstijdproces al heeft bepaald dat het specifieke lid moet worden aangeroepen, mogelijk door overbelastingsresolutie toe te passen op een set kandidaatfunctieleden.

Voor het beschrijven van het aanroepproces worden functieleden onderverdeeld in twee categorieën:

  • Statische functieleden. Dit zijn statische methoden, statische eigenschapstoegangsors en door de gebruiker gedefinieerde operators. Statische functieleden zijn altijd niet-virtueel.
  • Leden van de instantiefunctie. Dit zijn instantiemethoden, instantieconstructors, instantie-eigenschapstoegangsmethoden en indexeertoevallers. Functieleden van een instantie zijn niet-virtueel of virtueel en worden altijd aangeroepen op een specifiek exemplaar. Het exemplaar wordt berekend door een exemplaarexpressie en wordt toegankelijk binnen het functielid als this (§12.8.14). Voor een exemplaarconstructor wordt de exemplaarexpressie beschouwd als het zojuist toegewezen object.

De uitvoering van een aanroep van een functielid bestaat uit de volgende stappen, waarbij M het functielid is en, als M een exemplaarlid is, E de exemplaarexpressie is:

  • Als M een statisch functielid is:

    • De lijst met argumenten wordt geëvalueerd zoals beschreven in §12.6.2.
    • M wordt aangeroepen.
  • Als het type E een waardetype Vis, en M is gedeclareerd of overschreven in V:

    • E wordt geëvalueerd. Als deze evaluatie een uitzondering veroorzaakt, worden er geen verdere stappen uitgevoerd. Voor een instantieconstructor bestaat deze evaluatie uit het toewijzen van opslag (meestal uit een uitvoeringsstack) voor het nieuwe object. In dit geval wordt E geclassificeerd als een variabele.
    • Als E niet is geclassificeerd als een variabele of als V geen leesbare struct is (§16.2.2) en E een van de volgende is:
      • een invoerparameter (§15.6.2.3.2) of
      • een readonly veld (§15.5.3), of
      • een readonly verwijzingsvariabele of retourwaarde (§9,7)

    vervolgens wordt een tijdelijke lokale variabele van het type Egemaakt en wordt de waarde van E aan die variabele toegewezen. E wordt vervolgens opnieuw geclassificeerd als een verwijzing naar die tijdelijke lokale variabele. De tijdelijke variabele is toegankelijk als this binnen M, maar niet op een andere manier. Dus alleen wanneer E kan worden geschreven, kan de beller de wijzigingen observeren die M aan thisaanbrengt.

    • De lijst met argumenten wordt geëvalueerd zoals beschreven in §12.6.2.
    • M wordt aangeroepen. De variabele waarnaar wordt verwezen door E wordt de variabele waarnaar wordt verwezen door this.
  • Anders:

    • E wordt geëvalueerd. Als deze evaluatie een uitzondering veroorzaakt, worden er geen verdere stappen uitgevoerd.
    • De lijst met argumenten wordt geëvalueerd zoals beschreven in §12.6.2.
    • Als het type E een value_typeis, wordt een boxing-conversie (§10.2.9) uitgevoerd om E te converteren naar een class_type, en wordt E in de volgende stappen als die class_type beschouwd. Als de value_type een enum_typeis, wordt de class_type anders System.Enum;, wordt deze System.ValueType.
    • De waarde van E is gecontroleerd op geldigheid. Als de waarde van E null is, wordt er een System.NullReferenceException gegenereerd en worden er geen verdere stappen uitgevoerd.
    • De implementatie van het functielid dat moet worden aangeroepen, wordt bepaald:
      • Als het binding-time type van E een interface is, is het functielid dat moet worden aangeroepen de implementatie van M geleverd door het runtime-type van de instantie waar Enaar verwijst. Dit functielid wordt bepaald door de interfacetoewijzingsregels (§18.6.5) toe te passen om de implementatie van M te bepalen die wordt geleverd door het runtimetype van het exemplaar waarnaar wordt verwezen door E.
      • Anders, als M een virtueel functielid is, dan is het functielid dat aangeroepen moet worden de implementatie van M, geleverd door het uitvoeringstype van het exemplaar waarnaar Everwijst. Dit functielid wordt bepaald door de regels toe te passen voor het bepalen van de meest afgeleide implementatie (§15.6.4) van M met betrekking tot het uitvoeringstype van het exemplaar waarnaar wordt verwezen door E.
      • Anders is M het niet-virtuele functielid en is het functielid dat moet worden aangeroepen M zelf.
    • De implementatie van het functielid die in de bovenstaande stap is bepaald, wordt aangeroepen. Het object waarnaar wordt verwezen door E wordt het object waarnaar door dit wordt verwezen.

Het resultaat van de aanroep van een instantieconstructor (§12.8.17.2) is de waarde die is gemaakt. Het resultaat van de aanroep van een ander functielid is de eventueel geretourneerde waarde (§13.10.5) uit het lichaam.

12.6.6.2 Aanroepen op gewrapte instanties

Een functielid dat is geïmplementeerd in een value_type kan worden aangeroepen via een boxed exemplaar van die value_type in de volgende situaties:

  • Wanneer het functielid een overriding is van een methode die is geërfd van het class_type en wordt aangeroepen via een instance expressie van dat class_type.

    Opmerking: de class_type is altijd een van System.Object, System.ValueType of System.Enumeindnotitie

  • Wanneer het functielid een implementatie is van een interfacefunctielid en wordt aangeroepen via een exemplaarexpressie van een interface_type.
  • Wanneer het functielid wordt aangeroepen via een gemachtigde.

In deze situaties wordt het boxed-exemplaar beschouwd als een variabele van het type value_type, en deze variabele wordt de variabele waarnaar wordt verwezen binnen de aanroep van het functielid.

Opmerking: dit betekent met name dat wanneer een functielid wordt aangeroepen op een boxed exemplaar, het mogelijk is dat het functielid de waarde in het boxed-exemplaar wijzigt. eindnotitie

12.7 Deconstructie

Deconstructie is een proces waarbij een expressie wordt omgezet in een tuple van afzonderlijke expressies. Deconstructie wordt gebruikt wanneer het doel van een eenvoudige toewijzing een tuple-expressie is, om waarden te verkrijgen die aan de elementen van die tuple moeten worden toegekend.

Een expressie E wordt gedeconstrueerd tot een tuple-expressie met n elementen op de volgende manier:

  • Als E een tuple-expressie is met n elementen, is het resultaat van de deconstructie de expressie E zichzelf.
  • Anders, als E een tupletype (T1, ..., Tn) met n elementen heeft, dan wordt E geëvalueerd in een tijdelijke variabele __ven is het resultaat van de deconstructie de expressie (__v.Item1, ..., __v.Itemn).
  • Als de expressie E.Deconstruct(out var __v1, ..., out var __vn) tijdens het compileren wordt omgezet in een unieke instantie of extensiemethode, wordt die expressie geëvalueerd en is het resultaat van de deconstructie de expressie (__v1, ..., __vn). Een dergelijke methode wordt een deconstructorgenoemd.
  • Anders kan E niet worden gedeconstrueerd.

Hier verwijzen __v en __v1, ..., __vn naar onzichtbare en ontoegankelijke tijdelijke variabelen.

Opmerking: een expressie van het type dynamic kan niet worden gedeconstrueerd. eindnotitie

12.8 Primaire expressies

12.8.1 Algemeen

Primaire expressies bevatten de eenvoudigste vormen van expressies.

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
    ;

Opmerking: deze grammaticaregels zijn niet gereed voor ANTLR, omdat ze deel uitmaken van een reeks wederzijds recursieve regels (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 en pointer_element_access) die niet door ANTLR worden verwerkt. Standaardtechnieken kunnen worden gebruikt om de grammatica te transformeren en de wederzijdse linker recursie te verwijderen. Dit is niet gedaan omdat niet alle parseringsstrategieën dit vereisen (bijvoorbeeld een LALR-parser niet) en dit zou de structuur en beschrijving verdoezelen. eindnotitie

pointer_member_access (§23.6.3) en pointer_element_access (§23.6.4) zijn alleen beschikbaar in onveilige code (§23).

Primaire uitdrukkingen worden verdeeld tussen array_creation_expressionen primary_no_array_creation_expression. Door array_creation_expression op deze manier te behandelen, in plaats van deze samen met de andere eenvoudige expressievormen weer te geven, kan de grammatica verwarrende code zoals... uitsluiten.

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

wat anders zou worden geïnterpreteerd als

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

12.8.2 Letterlijke tekens

Een primary_expression die bestaat uit een letterlijke (§6.4,5) wordt geclassificeerd als een waarde.

12.8.3 Geïnterpoleerde tekenreeksexpressies

Een interpolated_string_expression bestaat uit $, $@of @$, direct gevolgd door tekst binnen " tekens. Binnen de tekst tussen aanhalingstekens zijn er nul of meer interpolaties, begrensd door de tekens { en }, die elk een expressie en optionele opmaakspecificaties bevatten.

Geïnterpoleerde tekenreeksexpressies hebben twee vormen; regular (interpolated_regular_string_expression) en verbatim (interpolated_verbatim_string_expression); die lexisch vergelijkbaar zijn met, maar semantisch verschillen van, de twee vormen van letterlijke tekenreeksen (§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
    : '}}'
    ;

Zes van de hierboven gedefinieerde lexicale regels zijn als volgt contextgevoelige:

regel contextuele vereisten
Interpolated_Regular_String_Mid Alleen erkend na een Interpolated_Regular_String_Start, tussen eventuele volgende interpolaties en vóór de overeenkomstige Interpolated_Regular_String_End.
Regular_Interpolation_Format Alleen herkend binnen een regular_interpolation en wanneer de beginkomma (:) is niet genest binnen een haakje (haakjes/accolades/vierkant).
Interpolated_Regular_String_End Wordt alleen herkend na een Interpolated_Regular_String_Start en alleen indien tussenliggende tokens Interpolated_Regular_String_Mids zijn of tokens die deel uit kunnen maken van regular_interpolations, inclusief tokens voor alle interpolated_regular_string_expressions die binnen dergelijke interpolaties zijn verwerkt.
Interpolated_Verbatim_String_MidVerbatim_Interpolation_FormatInterpolated_Verbatim_String_End De erkenning van deze drie regels volgt op die van de overeenkomstige bovenstaande regels, waarbij elke vermelde reguliere grammaticaregel wordt vervangen door de bijbehorende exacte.

Opmerking: de bovenstaande regels zijn contextgevoelig omdat de definities overlappen met die van andere tokens in de taal. eindnotitie

Opmerking: de bovenstaande grammatica is niet gereed voor ANTLR vanwege de contextgevoelige lexicale regels. Net als bij andere lexergenerators ondersteunt ANTLR contextgevoelige lexicale regels, bijvoorbeeld met behulp van de lexicale modi, maar dit is een implementatiedetail en daarom geen deel van deze specificatie. eindnotitie

Een interpolated_string_expression wordt geclassificeerd als een waarde. Als deze direct wordt geconverteerd naar System.IFormattable of System.FormattableString met een impliciete geïnterpoleerde tekenreeksconversie (§10.2.5), heeft de geïnterpoleerde tekenreeksexpressie dat type. Anders is het van het type string.

Opmerking: de verschillen tussen de mogelijke typen een interpolated_string_expression kunnen worden bepaald uit de documentatie voor System.String (§C.2) en System.FormattableString (§C.3). eindnotitie

De betekenis van een interpolatie, zowel regular_interpolation als verbatim_interpolation, is het opmaken van de waarde van de expressie als een string volgens de indeling die is opgegeven door de Regular_Interpolation_Format of Verbatim_Interpolation_Format, of volgens een standaardindeling voor het type expressie. De opgemaakte tekenreeks wordt vervolgens gewijzigd door de interpolation_minimum_width, indien van toepassing, om de definitieve string te creëren die in de interpolated_string_expressionwordt geïnterpoleerd.

Opmerking: hoe de standaardindeling voor een type wordt bepaald, wordt beschreven in de documentatie voor System.String (§C.2) en System.FormattableString (§C.3). Beschrijvingen van standaardindelingen, die identiek zijn voor Regular_Interpolation_Format en Verbatim_Interpolation_Format, vindt u in de documentatie voor System.IFormattable (§C.4) en in andere typen in de standaardbibliotheek (§C). eindnotitie

In een interpolation_minimum_width heeft de constant_expression een impliciete conversie naar int. Laat de veldbreedte de absolute waarde van deze constante expressie zijn en laat de uitlijning overeenkomen met het teken (positief of negatief) van de waarde van deze constante expressie:

  • Als de waarde van de veldbreedte kleiner is dan of gelijk is aan de lengte van de opgemaakte tekenreeks, wordt de opgemaakte tekenreeks niet gewijzigd.
  • Anders wordt de opgemaakte tekenreeks opgevuld met spatietekens, zodat de lengte gelijk is aan de veldbreedte:
    • Als de uitlijning positief is, wordt de opgemaakte tekenreeks rechts uitgelijnd door de opvulling aan te passen,
    • Anders wordt deze links uitgelijnd door de opvulling toe te voegen.

De algemene betekenis van een interpolated_string_expression, inclusief de bovenstaande opmaak en opvulling van interpolaties, wordt gedefinieerd door een conversie van de expressie naar een methodeaanroep: als het type expressie wordt System.IFormattable of System.FormattableString die methode is System.Runtime.CompilerServices.FormattableStringFactory.Create (§C.3) die een waarde van het type System.FormattableStringretourneert ; anders wordt het type string en wordt de methode string.Format (§C.2) die een waarde van het type stringretourneert .

In beide gevallen bestaat de argumentenlijst van de aanroep uit een letterlijke tekenreeks met notatietekenreeks letterlijke met notatiespecificaties voor elke interpolatie en een argument voor elke expressie die overeenkomt met de indelingsspecificaties.

De letterlijke notatietekenreeks wordt als volgt samengesteld, waarbij N het aantal interpolaties is in de interpolated_string_expression . De letterlijke notatietekenreeks bestaat uit, in volgorde:

  • De tekens van de Interpolated_Regular_String_Start of Interpolated_Verbatim_String_Start
  • De tekens van de Interpolated_Regular_String_Mid of Interpolated_Verbatim_String_Mid, indien aanwezig.
  • Als N ≥ 1 voor elk getal I van 0 tot N-1.
    • Een tijdelijke aanduidingsspecificatie:
      • Een linker accolade ({) teken
      • De decimale weergave van I
      • Als de bijbehorende regular_interpolation of verbatim_interpolation vervolgens een interpolation_minimum_widthheeft, wordt er een komma (,) gevolgd door de decimale weergave van de waarde van de constant_expression
      • De tekens van de Regular_Interpolation_Format of Verbatim_Interpolation_Format, indien van toepassing, van de bijbehorende regular_interpolation of verbatim_interpolation
      • Een rechter accolade (}) karakter
    • De tekens van de Interpolated_Regular_String_Mid of Interpolated_Verbatim_String_Mid die onmiddellijk na de bijbehorende interpolatie komen, indien van toepassing
  • Ten slotte de tekens van de Interpolated_Regular_String_End of Interpolated_Verbatim_String_End.

De volgende argumenten zijn de expressies uit de interpolaties, indien van toepassing, in volgorde.

Wanneer een interpolated_string_expression meerdere interpolaties bevat, worden de expressies in deze interpolaties geëvalueerd in tekstvolgorde van links naar rechts.

voorbeeld van:

In dit voorbeeld worden de volgende formatspecificaties gebruikt:

  • de X-indelingsspecificatie waarmee gehele getallen als hoofdletter-hexadecimaal worden opgemaakt,
  • de standaardindeling voor een string waarde is de waarde zelf,
  • positieve uitlijningswaarden die rechts uitlijnen binnen de opgegeven minimale veldbreedte,
  • negatieve uitlijningswaarden die links uitvullen binnen de opgegeven minimale veldbreedte,
  • gedefinieerde constanten voor de interpolation_minimum_widthen
  • dat {{ en }} respectievelijk zijn opgemaakt als { en }.

Gegeven:

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

Dan:

geïnterpoleerde tekenreeksexpressie equivalente betekenis als string waarde
$"{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"

einde voorbeeld

12.8.4 Eenvoudige namen

Een simple_name bestaat uit een id, eventueel gevolgd door een lijst met typeargumenten:

simple_name
    : identifier type_argument_list?
    ;

Een simple_name heeft de vorm I of de vorm I<A₁, ..., Aₑ>, waarbij I een enkele identificator is en I<A₁, ..., Aₑ> een optionele type-argumentenlijstis. Als er geen type_argument_list is opgegeven, wordt e als nul beschouwd. De simple_name wordt als volgt geëvalueerd en geclassificeerd:

  • Als e nul is en de simple_name wordt weergegeven in een lokale ruimte voor variabeledeclaratie (§7.3) die rechtstreeks een lokale variabele, parameter of constante met naam Ibevat, verwijst de simple_name naar die lokale variabele, parameter of constante en wordt geclassificeerd als een variabele of waarde.
  • Als e nul is en de simple_name wordt weergegeven in een algemene methodedeclaratie, maar buiten de kenmerken van de method_declaration, en als die declaratie een typeparameter met de naam Ibevat, verwijst de simple_name naar die typeparameter.
  • Anders geldt voor elk exemplaartype T (§15.3.2), te beginnen met het exemplaartype van de onmiddellijk insluitende typedeclaratie en door te gaan met het exemplaartype van elke insluitende klasse- of structdeclaratie (indien van toepassing):
    • Als e nul is en de declaratie van T een typeparameter met de naam Ibevat, verwijst de simple_name naar die typeparameter.
    • Anders, als een ledenopzoeking (§12,5) van I in T met e typeargumenten een overeenkomst oplevert:
      • Als T het exemplaartype is van het onmiddellijk ingesloten klasse- of structtype en de zoekactie een of meer methoden identificeert, is het resultaat een methodegroep met een bijbehorende exemplaarexpressie van this. Als een lijst met typeargumenten is opgegeven, wordt deze gebruikt bij het aanroepen van een algemene methode (§12.8.10.2).
      • Als T het exemplaartype is van de onmiddellijk insluitende klasse of struct, als het zoeken een exemplaarlid identificeert en als de verwijzing optreedt in het blok van een exemplaarconstructor, een instantievemethode of een instantietoegangsmethode (§12.2.1), is het resultaat hetzelfde als een lidtoegang (§12.8.7) van de vorm this.I. Dit kan alleen gebeuren wanneer e nul is.
      • Anders is het resultaat hetzelfde als een lidtoegang (§12.8.7) van het formulier T.I of T.I<A₁, ..., Aₑ>.
  • Anders worden voor elke naamruimte N, beginnend met de naamruimte waarin de eenvoudige naam voorkomt, door te gaan met elke insluitende naamruimte (indien van toepassing) en eindigend met de globale naamruimte, worden de volgende stappen geëvalueerd totdat een entiteit is gevonden.
    • Als e nul is en I de naam van een naamruimte in Nis, dan:
      • Als de locatie waar de simple_name plaatsvindt, wordt ingesloten door een naamruimtedeclaratie voor N en de naamruimtedeclaratie een extern_alias_directive of using_alias_directive bevat die de naam I koppelt aan een naamruimte of type, is de simple_name niet eenduidig en treedt er een compilatietijdfout op.
      • Anders verwijst de simple_name naar de naamruimte met de naam I in N.
    • Anders, als N een toegankelijk type bevat met de naam I en typeparameters voor e, dan:
      • Als e nul is en de locatie waar de simple_name plaatsvindt, wordt ingesloten door een naamruimtedeclaratie voor N en bevat de naamruimtedeclaratie een extern_alias_directive of using_alias_directive die de naam I koppelt aan een naamruimte of type, is de simple_name dubbelzinnig en treedt er een compilatietijdfout op.
      • Anders verwijst de namespace_or_type_name naar het type dat is samengesteld met de opgegeven typeargumenten.
    • Anders, als de locatie waar de simple_name plaatsvindt, wordt ingesloten door een naamruimtedeclaratie voor N:
      • Als e nul is en de naamruimtedeclaratie een extern_alias_directive of using_alias_directive bevat die de naam I koppelt aan een geïmporteerde naamruimte of -type, verwijst de simple_name naar die naamruimte of dat type.
      • Anders, als de naamruimten die door de using_namespace_directives van de declaratie van de naamruimte zijn geïmporteerd, precies één type bevatten met de naam I en e type parameters, verwijst de simple_name naar dat type gevormd met de gespecificeerde typeargumenten.
      • Als de naamruimten die zijn geïmporteerd door de using_namespace_directives van de naamruimtedeclaratie meer dan één type bevatten met de naam I en e typeparameters, is de simple_name niet eenduidig en treedt er een compilatiefout op.

    Opmerking: deze hele stap is exact parallel aan de bijbehorende stap in de verwerking van een namespace_or_type_name (§7.8). eindnotitie

  • Als e nul is en I de identificator _is, is de simple_name een eenvoudige verworpene, een vorm van declaratie-uitdrukking (§12.17).
  • Anders is de simple_name niet gedefinieerd en treedt er een compilatietijdfout op.

12.8.5 Haakjes geplaatste expressies

Een parenthesized_expression bestaat uit een expressie tussen haakjes.

parenthesized_expression
    : '(' expression ')'
    ;

Een parenthesized_expression wordt geëvalueerd door de expressie tussen de haakjes te evalueren. Als de expressie tussen de haakjes een naamruimte of type aangeeft, treedt er een compilatietijdfout op. Anders is het resultaat van de parenthesized_expression het resultaat van de evaluatie van de ingesloten expressie.

12.8.6 Tuple-expressies

Een tuple_expression vertegenwoordigt een tuple en bestaat uit twee of meer door komma's gescheiden en optioneel benoemde expressietussen haakjes. Een deconstruction_expression is een verkorte syntaxis voor een tuple die impliciet getypte declaratie-uitdrukkingen bevat.

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
    ;

Een tuple_expression wordt geclassificeerd als een tuple.

Een deconstruction_expressionvar (e1, ..., en) is een afkorting voor de tuple_expression(var e1, ..., var en) en volgt hetzelfde gedrag. Dit geldt recursief voor geneste deconstruction_tuples in de deconstruction_expression. Elke identificator die binnen een deconstruction_expression wordt genest, introduceert dus een declaratie-uitdrukking (§12.17). Als gevolg hiervan kan een deconstruction_expression zich alleen aan de linkerkant van een eenvoudige opdracht voordoen.

Een tuple-expressie heeft een type als en alleen als elk van de elementexpressies Ei een type Tiheeft. Het type moet een tupletype van dezelfde ariteit zijn als de tuple-expressie, waarbij elk element wordt gegeven door het volgende:

  • Als het tuple-element in de overeenkomstige positie een naam heeft Ni, wordt het tupeltypeelement Ti Ni.
  • Als Ei in de vorm van Ni of E.Ni of E?.Ni is, dan wordt het tuplet-type-element Ti Ni, tenzij een van de volgende voorwaarden geldt:.
    • Een ander element van de tuple-expressie heeft de naam Niof
    • Een ander tuple-element zonder naam heeft een tuple-elementexpressie van de vorm Ni of E.Ni of E?.Ni, of
    • Ni is van de vorm ItemX, waarbij X een reeks niet-0geïnitieerde decimalen is die de positie van een tuple-element kunnen vertegenwoordigen en X niet de positie van het element vertegenwoordigt.
  • Anders moet het tupel-type-element Tizijn.

Een tuple-expressie wordt geëvalueerd door elk van de elementexpressies te evalueren in volgorde van links naar rechts.

Een tuple-waarde kan worden verkregen uit een tuple-expressie door deze te converteren naar een tupletype (§10.2.2.13), door deze opnieuw te classificeren als een waarde (§12.2.2)) of door deze te maken als doel van een deconstructerende toewijzing (§12.21.2).

voorbeeld van:

(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

In dit voorbeeld zijn alle vier de tuple-expressies geldig. De eerste twee, t1 en t2, gebruiken niet het type tuple-expressie, maar passen in plaats daarvan een impliciete tupleconversie toe. In het geval van t2is de impliciete tuple-conversie afhankelijk van de impliciete conversies van 2 naar long en van null naar string. De derde tuple-expressie heeft een type (int i, string)en kan daarom opnieuw worden geclassificeerd als een waarde van dat type. De declaratie van t4daarentegen is een fout: de tuple-expressie heeft geen type omdat het tweede element geen type heeft.

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

In dit voorbeeld zal je zien dat tuples soms kunnen leiden tot meerdere lagen van haakjes, vooral wanneer de tuple-expressie het enige argument is bij een methodeaanroep.

einde voorbeeld

12.8.7 Toegang tot leden

12.8.7.1 Algemeen

Een member_access bestaat uit een primary_expression, een predefined_typeof een qualified_alias_member, gevolgd door een "." token, gevolgd door een identifier, eventueel gevolgd door een 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'
    ;

De qualified_alias_member productie wordt gedefinieerd in §14.8.

Een member_access is of de vorm E.I of van de vorm E.I<A₁, ..., Aₑ>, waarbij E een primary_expression, predefined_type of qualified_alias_member is,I een enkele identifier is, en <A₁, ..., Aₑ> een optionele type_argument_listis. Als er geen type_argument_list is opgegeven, wordt e als nul beschouwd.

Een member_access met een primary_expression van het type dynamic is dynamisch gebonden (§12.3.3). In dit geval classificeert de compiler de lidtoegang als een eigenschapstoegang van het type dynamic. De onderstaande regels om de betekenis van de member_access te bepalen, worden vervolgens toegepast tijdens runtime, met behulp van het runtimetype in plaats van het compileertijdtype van de primary_expression. Als deze runtime-classificatie leidt tot een methodegroep, dan zal de toegang tot het lid bestaan uit de primary_expression binnen een invocation_expression.

De member_access wordt als volgt geëvalueerd en geclassificeerd:

  • Als e nul is en E een naamruimte is en E een geneste naamruimte met naam Ibevat, is het resultaat die naamruimte.
  • Als E een naamruimte is en E een toegankelijk type met een naam I en K typeparameters bevat, is het resultaat dat het type is samengesteld met de opgegeven typeargumenten.
  • Als E is geclassificeerd als een type, als E geen typeparameter is en als een lidzoekactie (§12,5) van I in E met K typeparameters een overeenkomst produceert, wordt E.I als volgt geëvalueerd en geclassificeerd:

    Opmerking: wanneer het resultaat van een dergelijke lidzoekactie een methodegroep is en K nul is, kan de methodegroep methoden met typeparameters bevatten. Hierdoor kunnen dergelijke methoden worden overwogen voor het afleiden van het typeargument. eindnotitie

    • Als I een type identificeert, is het resultaat dat type samengesteld met gegeven typeargumenten.
    • Als I een of meer methoden identificeert, is het resultaat een methodegroep zonder bijbehorende exemplaarexpressie.
    • Als I een statische eigenschap identificeert, is het resultaat een eigenschapstoegang zonder bijbehorende exemplaarexpressie.
    • Als I een statisch veld identificeert:
      • Als het veld alleen-lezen is en de verwijzing plaatsvindt buiten de statische constructor van de klasse of struct waarin het veld wordt gedeclareerd, dan is het resultaat een waarde, namelijk die van het statische veld I in E.
      • Anders is het resultaat een variabele, namelijk het statische veld I in E.
    • Als I een statische gebeurtenis identificeert:
      • Als de verwijzing plaatsvindt in de klasse of instructie waarin de gebeurtenis wordt gedeclareerd en de gebeurtenis is gedeclareerd zonder event_accessor_declarations (§15.8.1), wordt E.I precies verwerkt alsof I een statisch veld zijn.
      • Anders is het resultaat een gebeurtenistoegang zonder bijbehorende exemplaarexpressie.
    • Als I een constante identificeert, is het resultaat een waarde, namelijk de waarde van die constante.
    • Als I een opsommingslid identificeert, is het resultaat een waarde, namelijk de waarde van dat opsommingslid.
    • Anders is E.I een ongeldige lidverwijzing en treedt er een compilatiefout op.
  • Als E een eigenschapstoegang, indexeerfunctietoegang, variabele of waarde is, waarvan het type Tis en een lidzoekactie (§12,5) van I in T met K typeargumenten een overeenkomst oplevert, wordt E.I als volgt geëvalueerd en geclassificeerd:
    • Als E een eigenschap of indexeerfunctietoegang is, wordt de waarde van de toegang tot de eigenschap of indexeerfunctie verkregen (§12.2.2) en wordt E opnieuw geclassificeerd als een waarde.
    • Als I een of meer methoden identificeert, is het resultaat een methodegroep met een bijbehorende exemplaarexpressie van E.
    • Als I een exemplaareigenschap identificeert, is het resultaat een eigenschapstoegang met een bijbehorende exemplaarexpressie van E en een gekoppeld type dat het type van de eigenschap is. Als T een klassetype is, wordt het bijbehorende type gekozen uit de eerste declaratie of override van de eigenschap die wordt gevonden door te beginnen met Ten vervolgens door de basisklassen te zoeken.
    • Als T een class_type is en I een exemplaarveld van die class_typeidentificeert:
      • Als de waarde van Enullis, wordt er een System.NullReferenceException gegenereerd.
      • Als het veld anderszins readonly is en de verwijzing buiten de instantieconstructor van de klasse waarin het veld gedeclareerd wordt, dan is het resultaat een waarde, namelijk de waarde van het veld I in het object die door Ewordt gerefereerd.
      • Anders is het resultaat een variabele, namelijk het veld I in het object waarnaar wordt verwezen door E.
    • Als T een struct_type is en I een exemplaarveld van die struct_typeidentificeert:
      • Als E een waarde is, of als het veld alleen-lezen is en de verwijzing plaatsvindt buiten een instantieconstructor van de structuur waarin het veld is gedeclareerd, dan is het resultaat een waarde, namelijk die van het veld I binnen het door Egegeven structuurexemplaar.
      • Anders is het resultaat een variabele, namelijk het veld I in het struct-exemplaar dat is gegeven door E.
    • Als I een instantie-gebeurtenis identificeert:
      • Als de verwijzing plaatsvindt in de klasse of de struct waarin de gebeurtenis wordt gedeclareerd en de gebeurtenis is gedeclareerd zonder event_accessor_declarations (§15.8.1), en de verwijzing niet voorkomt als de linkerkant van a += of -= operator, wordt E.I precies verwerkt alsof I een exemplaarveld is.
      • Anders is het resultaat een gebeurtenistoegang met een bijbehorende exemplaarexpressie van E.
  • Anders wordt geprobeerd om E.I te verwerken als een aanroep van een uitbreidingsmethode (§12.8.10.3). Als dit mislukt, is E.I een ongeldige lidverwijzing en treedt er een bindingstijdfout op.

12.8.7.2 Identieke eenvoudige namen en typenamen

Als bij een lidtoegang van de vorm E.IE een enkele identificator is, en als de betekenis van E als een simple_name (§12.8.4) een constante, veld, eigenschap, lokale variabele of parameter is met hetzelfde type als de betekenis van E als een type_name (§7.8.1), dan zijn beide interpretaties van E toegestaan. Het opzoeken van leden van E.I is nooit ondubbelzinnig, aangezien I in beide gevallen noodzakelijkerwijs tot het type E behoort. Met andere woorden, de regel maakt simpelweg toegang tot de statische leden en geneste typen van E mogelijk waar anders een compilatiefout zou zijn opgetreden.

voorbeeld van:

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

Uitsluitend voor verklarende doeleinden, binnen de A-klasse, worden de exemplaren van de Color-identificator die naar het Color-type verwijzen, gemarkeerd met «...», terwijl degenen die naar het Color-veld verwijzen dat niet worden.

einde voorbeeld

12.8.8 Null-voorwaardelijke lidtoegang

Een null_conditional_member_access is een voorwaardelijke versie van member_access (§12.8.7) en is een bindingstijdfout als het resultaattype is void. Zie voor een null-voorwaardelijke expressie waarin het resultaattype kan worden void (§12.8.11).

Een null_conditional_member_access bestaat uit een primary_expression gevolgd door de twee tokens "?" en ".", gevolgd door een -id met een optionele type_argument_list, gevolgd door nul of meer dependent_accessdie door een null_forgiving_operatorkunnen worden gevolgd.

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?
    ;

Een null_conditional_member_access expressie E heeft de vorm P?.A. De betekenis van E wordt als volgt bepaald:

  • Als het type P een null-waardetype is:

    Laat T het type P.Value.Azijn.

    • Als T een typeparameter is waarvan niet bekend is of het een verwijzingstype of een waarde-type dat niet null is, optreedt er een fout tijdens de compilatietijd.

    • Als T een niet-null-waardetype is, wordt het type ET?en is de betekenis van E hetzelfde als de betekenis van:

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

      Behalve dat P slechts één keer wordt geëvalueerd.

    • Anders is het type ETen is de betekenis van E hetzelfde als de betekenis van:

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

      Behalve dat P slechts één keer wordt geëvalueerd.

  • Anders:

    Laat T het type expressie P.Azijn.

    • Als T een typeparameter is waarvan niet bekend is of het een verwijzingstype of een waarde-type dat niet null is, optreedt er een fout tijdens de compilatietijd.

    • Als T een niet-null-waardetype is, wordt het type ET?en is de betekenis van E hetzelfde als de betekenis van:

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

      Behalve dat P slechts één keer wordt geëvalueerd.

    • Anders is het type ETen is de betekenis van E hetzelfde als de betekenis van:

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

      Behalve dat P slechts één keer wordt geëvalueerd.

Opmerking: In een expressie van het formulier:

P?.A₀?.A₁

Als P naar null evalueert, worden noch A₀ noch A₁ geëvalueerd. Hetzelfde geldt als een expressie een reeks null_conditional_member_access of null_conditional_element_access§12.8.13 bewerkingen is.

eindnotitie

Een null_conditional_projection_initializer is een beperking van null_conditional_member_access en heeft dezelfde semantiek. Het gebeurt alleen als een projectie-initialisatiefunctie in een anonieme expressie voor het maken van objecten (§12.8.17.7).

12.8.9 Null-forgiving-expressies

12.8.9.1 Algemeen

De waarde, het type, de classificatie (§12.2) en de veilige context van een expressie (§16.4.12) is de waarde, het type, de classificatie en de veilige context van de primary_expression.

null_forgiving_expression
    : primary_expression null_forgiving_operator
    ;

null_forgiving_operator
    : '!'
    ;

Opmerking: de logische negatieoperators voor het postfix null-forgiving en voorvoegsel (§12.9.4), terwijl deze worden vertegenwoordigd door hetzelfde lexicale token (!), zijn verschillend. Alleen de laatstgenoemde kan worden overschreven (§15.10), de definitie van de null-forgiving-operator is vastgesteld. eindnotitie

Het is een compilatiefout om de null-vergevingsoperator meer dan één keer toe te passen op dezelfde uitdrukking, ongeacht tussenliggende haakjes.

voorbeeld: de volgende zijn allemaal ongeldig:

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

einde voorbeeld

De rest van deze subclause en de volgende subclauses zijn voorwaardelijk normatief.

Een compiler die statische null-statusanalyse uitvoert (§8.9.5) moet voldoen aan de volgende specificatie.

De null-vergevingsgezinde operator is een pseudo-bewerking op compilatietijd die wordt gebruikt om de compiler te voorzien van informatie voor de statische analyse van de null-status. Het heeft twee toepassingen: om de vastberadenheid van een compiler te overschrijven dat een expressie misschien null-; en om een compiler te overschrijven die een waarschuwing geeft met betrekking tot null-baarheid.

Het toepassen van de null-forgiving operator op een expressie waarvoor de statische null-analyse van een compiler geen waarschuwingen geeft, is geen fout.

12.8.9.2 Het overschrijven van een 'misschien null'-bepaling

In sommige gevallen kan de statische null-statusanalyse van een compiler bepalen dat een expressie de null-status heeft misschien null- en een diagnostische waarschuwing geeft wanneer andere informatie aangeeft dat de expressie niet null kan zijn. Het toepassen van de null-vergevingsoperator op een dergelijke expressie informeert de statische null-statusanalyse van de compiler dat de null-status zich in bevindt en niet in null-. Hierdoor wordt de diagnostische waarschuwing voorkomen en kan dit invloed hebben op lopende analyses.

Voorbeeld: Houd rekening met het volgende:

#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;

Als IsValidtrueretourneert, kan p veilig worden gedereferentieerd om toegang te krijgen tot de eigenschap Name en kan de waarschuwing "dereferencing van een mogelijk nullwaarde" worden onderdrukt met behulp van !.

einde voorbeeld

Voorbeeld: De null-forgiving operator moet voorzichtig worden gebruikt; overweeg het volgende:

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

Hier wordt de null-forgiving-operator toegepast op een waardetype en onderdrukt deze eventuele waarschuwingen voor x. Als x tijdens runtime echter null is, treedt er een uitzondering op omdat null niet naar intkan worden gecast.

einde voorbeeld

12.8.9.3 Overschrijven van andere null-analysewaarschuwingen

Naast het overschrijven van misschien null- bepaling zoals hierboven, kunnen er andere omstandigheden zijn waarin het wenselijk is om de statische null-statusanalyse van een compiler te overschrijven waarvoor een expressie een of meer waarschuwingen vereist. Het toepassen van de operator null-forgiving op een dergelijke expressie vraagt dat een compiler geen waarschuwingen voor de expressie uitgeeft. Als reactie kan een compiler ervoor kiezen om geen waarschuwingen uit te geven en kan het ook zijn verdere analyse wijzigen.

Voorbeeld: Houd rekening met het volgende:

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

De typen van methode Assign's parameters, lv & rv, zijn string?, waarbij lv een uitvoerparameter is en het een eenvoudige toewijzing uitvoert.

Methode M geeft de variabele svan het type stringdoor als uitvoerparameter van Assign. De gebruikte compiler geeft een waarschuwing omdat s geen nullable variabele is. Aangezien het tweede argument van Assignniet null kan zijn, wordt de operator null-forgiving gebruikt om de waarschuwing te vernietigen.

einde voorbeeld

Einde van voorwaardelijk normatieve tekst.

12.8.10 Aanroepexpressies

12.8.10.1 Algemeen

Een invocation_expression wordt gebruikt om een methode aan te roepen.

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

De primary_expression kan een null_forgiving_expression zijn als en alleen als het een delegate_typeheeft.

Een invocation_expression is dynamisch gebonden (§12.3.3) indien ten minste één van de volgende bewaringen bevat:

  • De primary_expression heeft het compilatietijdstype dynamic.
  • Ten minste één argument van de optionele argument_list heeft het type compileertijd dynamic.

In dit geval classificeert de compiler de invocation_expression als een waarde van het type dynamic. De onderstaande regels om de betekenis van de invocation_expression te bepalen, worden vervolgens tijdens de uitvoering toegepast met behulp van het uitvoeringstijdtype in plaats van het compileertijdtype van de primary_expression en argumenten met het compileertijdtype dynamic. Als het primary_expression geen compileertijdtype heeft dynamic, ondergaat de methodeaanroep een beperkte compileertijdcontrole, zoals beschreven in §12.6.5.

De primary_expression van een invocation_expression moet een methodegroep of een waarde van een delegate_typezijn. Als de primary_expression een methodegroep is, is de invocation_expression een methode-aanroep (§12.8.10.2). Als de primary_expression een waarde van een delegate_typeis, is de invocation_expression een gemachtigde aanroep (§12.8.10.4). Als de primary_expression geen methodegroep of een waarde van een delegate_typeis, treedt er een bindingstijdfout op.

De optionele argument_list (§12.6.2) bevat waarden of variabele verwijzingen voor de parameters van de methode.

Het resultaat van het evalueren van een invocation_expression wordt als volgt geclassificeerd:

  • Als de invocation_expression een methode zonder waarde aanroept (§15.6.1) of een machtiging zonder waarde aanroept, is er geen resultaat. Een expressie die als niets is geclassificeerd, is alleen toegestaan in de context van een statement_expression (§13.7) of als hoofdtekst van een lambda_expression (§12.19). Anders treedt er een bindingstijdfout op.
  • Anders, als de invocation_expression een methode als 'returns-by-ref' (§15.6.1) of een 'returns-by-ref' gedelegeerde aanroept, is het resultaat een variabele met een type dat overeenkomt met het retourtype van de methode of gedelegeerde. Als het gaat om de aanroep van een instantiemethode en de ontvanger van een klassetype Tis, wordt het bijbehorende type gekozen uit de eerste declaratie of overschrijving van de methode die wordt gevonden door te beginnen met T en de basisklassen door te zoeken.
  • Anders roept de invocation_expression een methode aan die een waarde retourneert (§15.6.1) of een delegate die een waarde retourneert, en het resultaat is een waarde met het type dat overeenkomt met het retourtype van de methode of de delegate. Als het gaat om de aanroep van een instantiemethode en de ontvanger van een klassetype Tis, wordt het bijbehorende type gekozen uit de eerste declaratie of overschrijving van de methode die wordt gevonden door te beginnen met T en de basisklassen door te zoeken.

12.8.10.2 Methode-aanroepen

Voor een methode-aanroep moet de primary_expression van de invocation_expression een methodegroep zijn. De methodegroep identificeert de ene methode die moet worden aangeroepen of de set overbelaste methoden waaruit een specifieke methode moet worden gekozen die moet worden aangeroepen. In het laatste geval is de bepaling van de specifieke methode die moet worden aangeroepen, gebaseerd op de context die wordt geboden door de typen argumenten in de argument_list.

De bindingstijdverwerking van een methodeaanroep van het formulier M(A), waarbij M een methodegroep is (mogelijk inclusief een type_argument_list), en A een optionele argument_listis, bestaat uit de volgende stappen:

  • De set kandidaatmethoden voor de methode-aanroep wordt samengesteld. Voor elke methode F gekoppeld aan de methodegroep M:
    • Als F niet-algemeen is, is F een kandidaat wanneer:
      • M heeft geen lijst met typeargumenten en
      • F geldt voor A (§12.6.4.2).
    • Als F algemeen is en M geen lijst met typeargumenten heeft, is F een kandidaat wanneer:
      • Type deductie (§12.6.3) slaagt, waarbij een lijst met typeargumenten voor de aanroep wordt afgeleid en
      • Zodra de uitgestelde typeargumenten worden vervangen door de overeenkomstige parametertypeparameters, voldoen alle samengestelde typen in de parameterlijst van F aan hun beperkingen (§8.4.5) en is de parameterlijst van F van toepassing op A (§12.6.4.2)
    • Als F algemeen is en M een lijst met typeargumenten bevat, is F een kandidaat wanneer:
      • F hetzelfde aantal parameters van het methodetype heeft als die zijn opgegeven in de lijst met typeargumenten en
      • Zodra de typeargumenten worden vervangen door de overeenkomstige parametertypeparameters, voldoen alle samengestelde typen in de parameterlijst van F aan hun beperkingen (§8.4.5), en is de parameterlijst van F van toepassing met betrekking tot A (§12.6.4.2).
  • De set kandidaatmethoden wordt beperkt tot alleen methoden van de meest afgeleide typen: voor elke methode C.F in de set, waarbij C het type is waarin de methode F wordt gedeclareerd, worden alle methoden die zijn gedeclareerd in een basistype van C uit de set verwijderd. Als C bovendien een ander klassetype is dan object, worden alle methoden die in een interfacetype zijn gedeclareerd, uit de set verwijderd.

    Opmerking: deze laatste regel heeft alleen een effect wanneer de methodegroep het resultaat was van een opzoekactie van een lid op een typeparameter met een andere effectieve basisklasse dan object en een niet-lege effectieve interfaceset. eindnotitie

  • Als de resulterende set kandidaatmethoden leeg is, worden verdere verwerking van de volgende stappen stopgezet en wordt in plaats daarvan geprobeerd de aanroep te verwerken als een extensiemethodeaanroep (§12.8.10.3). Als dit mislukt, bestaan er geen toepasselijke methoden en treedt er een bindingstijdfout op.
  • De beste methode van de set kandidaatmethoden wordt geïdentificeerd met behulp van de overbelastingsoplossingsregels van §12.6.4. Als één beste methode niet kan worden geïdentificeerd, is de aanroep van de methode dubbelzinnig en treedt er een bindingstijdfout op. Bij het uitvoeren van overbelastingsresolutie worden de parameters van een algemene methode overwogen na vervanging van de typeargumenten (opgegeven of afgeleid) voor de bijbehorende parameters van het methodetype.

Zodra een methode is geselecteerd en gevalideerd tijdens bindingstijd door de bovenstaande stappen, wordt de werkelijke aanroep van runtime verwerkt volgens de regels van de aanroep van functieleden die worden beschreven in §12.6.6.

Opmerking: Het intuïtieve effect van de hierboven beschreven oplossingsregels is als volgt: Als u de specifieke methode wilt vinden die wordt aangeroepen door een methodeaanroep, begint u met het type dat wordt aangegeven door de methodeaanroep en gaat u verder met de overnameketen totdat ten minste één toepasselijke, toegankelijke, niet-onderdrukkingsmethodedeclaratie wordt gevonden. Voer vervolgens type-inferentie en overbelasting resolutie uit op de reeks toepasselijke, toegankelijke, niet-override methoden die in dat type zijn gedeclareerd en roep de aldus geselecteerde methode aan. Als er geen methode is gevonden, probeert u in plaats daarvan de aanroep te verwerken als een aanroep van de extensiemethode. eindnotitie

12.8.10.3 Uitbreidingsmethode aanroepen

In een methode-aanroep (§12.6.6.2) van een van de formulieren

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

als bij de normale verwerking van de aanroep geen toepasselijke methoden worden gevonden, wordt geprobeerd de constructie te verwerken als een aanroep van de extensiemethode. Als «expr» of een van de «args» compileertijdtype dynamicheeft, zijn extensiemethoden niet van toepassing.

Het doel is om de beste type_nameCte vinden, zodat de bijbehorende statische methode-aanroep kan plaatsvinden:

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

Een extensiemethode Cᵢ.Mₑ is in aanmerking komend als:

  • Cᵢ is een niet-generieke, niet-geneste klasse
  • De naam van Mₑ is identificator
  • Mₑ is toegankelijk en van toepassing wanneer deze wordt toegepast op de argumenten als een statische methode, zoals hierboven wordt weergegeven
  • Er bestaat een impliciete identiteit-, verwijzings- of boxing-conversie van expr naar het type van de eerste parameter van Mₑ.

De zoekopdracht naar C gaat als volgt:

  • Beginnend met de dichtstbijzijnde naamruimtedeclaratie, doorgaand met elke declaratie van de naamruimte en eindigend met de bijbehorende compilatie-eenheid, worden opeenvolgende pogingen gedaan om een kandidaatset met extensiemethoden te vinden:
    • Als de opgegeven naamruimte of compilatie-eenheid rechtstreeks niet-algemene typedeclaraties bevat Cᵢ met in aanmerking komende uitbreidingsmethoden Mₑ, is de set van deze uitbreidingsmethoden de kandidaatset.
    • Als naamruimten die worden geïmporteerd met behulp van naamruimte-instructies in de opgegeven naamruimte of compilatie-eenheid rechtstreeks niet-algemene typedeclaraties Cᵢ met in aanmerking komende extensiemethoden Mₑbevatten, is de set van deze extensiemethoden de kandidaatset.
  • Als er geen kandidaatset wordt gevonden in een naamruimtedeclaratie of compilatie-eenheid, treedt er een compilatietijdfout op.
  • Anders wordt overbelastingsafhandeling toegepast op de kandidaatset zoals beschreven in §12.6.4. Als er geen enkele beste methode wordt gevonden, treedt er een compilatietijdfout op.
  • C is het type waarin de beste methode wordt gedeclareerd als een extensiemethode.

Met behulp van C als doel wordt de methode-aanroep vervolgens verwerkt als een statische methode-aanroep (§12.6.6).

Opmerking: in tegenstelling tot een aanroep van een instantiemethode wordt er geen uitzondering gegenereerd wanneer expr resulteert in een null-verwijzing. In plaats daarvan wordt deze null waarde doorgegeven aan de extensiemethode, zoals bij een normale statische methode-aanroep. Het is aan de implementatie van de uitbreidingsmethode om te bepalen hoe moet worden gereageerd op een dergelijke oproep. eindnotitie

De voorgaande regels betekenen dat instantiemethoden voorrang hebben op extensiemethoden, dat extensiemethoden die beschikbaar zijn in declaraties van binnennaamruimten voorrang hebben op extensiemethoden die beschikbaar zijn in declaraties van buitennaamruimten, en dat extensiemethoden die rechtstreeks in een naamruimte zijn gedeclareerd voorrang hebben op extensiemethoden die in dezelfde naamruimte zijn geïmporteerd met een using namespace-instructie.

voorbeeld van:

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

In het voorbeeld heeft de methode van Bvoorrang op de eerste extensiemethode en heeft Cmethode voorrang op beide uitbreidingsmethoden.

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

De uitvoer van dit voorbeeld is:

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

D.G heeft voorrang op C.Gen E.F heeft voorrang op zowel D.F als C.F.

einde voorbeeld

12.8.10.4 Aanroepen delegeren

Voor de aanroep van een delegate moet de primary_expression van de invocation_expression een waarde zijn van een delegate_type. Aangezien de delegate_type bovendien een functielid met dezelfde parameterlijst als de delegate_typeis, is het delegate_type van toepassing (§12.6.4.2) met betrekking tot de argument_list van het invocation_expression.

De runtime-verwerking van een delegate-aanroep van de vorm D(A), waarbij D een primary_expression is van een delegate_type en A een optionele argument_listis, bestaat uit de volgende stappen:

  • D wordt geëvalueerd. Als deze evaluatie een uitzondering veroorzaakt, worden er geen verdere stappen uitgevoerd.
  • De lijst met argumenten A wordt geëvalueerd. Als deze evaluatie een uitzondering veroorzaakt, worden er geen verdere stappen uitgevoerd.
  • De waarde van D is gecontroleerd op geldigheid. Als de waarde van D is null, wordt er een System.NullReferenceException gegenereerd en worden er geen verdere stappen uitgevoerd.
  • Anders is D een verwijzing naar een gedelegeerde instantie. Aanroepen van functieleden (§12.6.6) worden uitgevoerd op elk van de aanroepbare entiteiten in de aanroeplijst van de gemachtigde. Voor aanroepbare entiteiten die bestaan uit een instantie en een instantiemethode, is de instantie voor de oproep de instantie in de aanroepbare entiteit.

Zie §20.6 voor meer informatie over meerdere aanroeplijsten zonder parameters.

12.8.11 Null-voorwaardelijke aanroepuitdrukking

Een null_conditional_invocation_expression is syntactisch gezien ofwel een null_conditional_member_access (§12.8.8) of een null_conditional_element_access (§12.8.13), waarbij het laatste dependent_access een aanroepuitdrukking is (§12.8.10).

Een null_conditional_invocation_expression vindt plaats in het kader van een statement_expression (§13.7), anonymous_function_body (§12.19.1) of method_body (§15.6.1).

In tegenstelling tot het syntactisch equivalente null_conditional_member_access of null_conditional_element_access, kan een null_conditional_invocation_expression worden geclassificeerd als niets.

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

De optionele null_forgiving_operator kan worden opgenomen als en alleen als de null_conditional_member_access of null_conditional_element_access een delegate_typeheeft.

Een null_conditional_invocation_expression expressie E is van de vorm P?A; waarbij A de rest van de syntactisch equivalente null_conditional_member_access of null_conditional_element_accessis, begint A daarom met . of [. Laat PA de samenvoeging van P en Aaanduiden.

Wanneer E optreedt als een statement_expression, is de betekenis van E hetzelfde als de betekenis van de -instructie.

if ((object)P != null) PA

behalve dat P slechts één keer wordt geëvalueerd.

Wanneer E voorkomt als een anonymous_function_body of method_body, is de betekenis van E afhankelijk van de classificatie:

  • Als E als niets wordt geclassificeerd, is de betekenis ervan hetzelfde als de betekenis van het blok:

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

    behalve dat P slechts één keer wordt geëvalueerd.

  • Anders is de betekenis van E hetzelfde als de betekenis van het blok:

    { return E; }
    

    en op zijn beurt hangt de betekenis van dit blok ervan af of E syntactisch gelijk is aan een null_conditional_member_access (§12.8.8) of null_conditional_element_access (§12.8.13).

12.8.12 Elementtoegang

12.8.12.1 Algemeen

Een element_access bestaat uit een primary_no_array_creation_expression, gevolgd door een ‘[’ token, gevolgd door een argument_list, gevolgd door een ‘]’ token. De argument_list bestaat uit een of meer arguments, gescheiden door komma's.

element_access
    : primary_no_array_creation_expression '[' argument_list ']'
    ;

De argument_list van een element_access mag geen out of ref argumenten bevatten.

Een element_access is dynamisch gebonden (§12.3.3) indien ten minste een van de volgende bewaringen geldt:

  • De primary_no_array_creation_expression heeft het type compilatietijd dynamic.
  • Ten minste één van de expressies in de argument_list heeft tijdens compileertijd het type dynamic en de primary_no_array_creation_expression heeft geen arraytype.

In dit geval classificeert de compiler de element_access als een waarde van het type dynamic. De onderstaande regels om de betekenis van de element_access te bepalen, worden vervolgens tijdens runtime toegepast met behulp van het runtimetype in plaats van het type compileertijd van die van de primary_no_array_creation_expression en argument_list expressies met het type compileertijd dynamic. Als de primary_no_array_creation_expression geen compilatietijdtype heeft dynamic, ondergaat de toegang tot het element een beperkte controle tijdens compilatietijd, zoals beschreven in §12.6.5.

Als de primary_no_array_creation_expression van een element_access een waarde van een array_typeis, is de element_access een matrixtoegang (§12.8.12.2). Anders is de primary_no_array_creation_expression een variabele of waarde van een klasse, struct of interfacetype met een of meer indexers, in welk geval de element_access een indexertoegang is (§12.8.12.3).

12.8.12.2 Matrixtoegang

Voor een arraytoegang moet de primary_no_array_creation_expression van de element_access een waarde zijn van een array_type. Bovendien mag de argument_list van een matrixtoegang geen benoemde argumenten bevatten. Het aantal expressies in de argument_list moet gelijk zijn aan de rang van de array_typeen elke expressie moet van het type int, uint, longof ulong, zijn of impliciet worden omgezet in een of meer van deze typen.

Het resultaat van het evalueren van een matrixtoegang is een variabele van het elementtype van de matrix, namelijk het matrixelement dat is geselecteerd door de waarde(s) van de expressies in de argument_list.

De runtimeverwerking van een matrixtoegang van het formulier P[A], waarbij P een primary_no_array_creation_expression is van een array_type en A een argument_listis, bestaat uit de volgende stappen:

  • P wordt geëvalueerd. Als deze evaluatie een uitzondering veroorzaakt, worden er geen verdere stappen uitgevoerd.
  • De indexexpressies van de argument_list worden op volgorde geëvalueerd, van links naar rechts. Na de evaluatie van elke indexexpressie wordt een impliciete conversie (§10,2) uitgevoerd op een van de volgende typen: int, uint, long, ulong. Het eerste type in deze lijst waarvoor een impliciete conversie bestaat, wordt gekozen. Als de indexexpressie bijvoorbeeld van het type short is, wordt een impliciete conversie naar int uitgevoerd, omdat impliciete conversies van short naar int en van short naar long mogelijk zijn. Als de evaluatie van een indexexpressie of de daaropvolgende impliciete conversie een uitzondering veroorzaakt, worden er geen verdere indexexpressies geëvalueerd en worden er geen verdere stappen uitgevoerd.
  • De waarde van P is gecontroleerd op geldigheid. Als de waarde van P is null, wordt er een System.NullReferenceException gegenereerd en worden er geen verdere stappen uitgevoerd.
  • De waarde van elke expressie in de argument_list wordt gecontroleerd aan de hand van de werkelijke limieten van elke dimensie van het matrixexemplaar waarnaar wordt verwezen door P. Als een of meer waarden buiten het bereik vallen, wordt er een System.IndexOutOfRangeException gegenereerd en worden er geen verdere stappen uitgevoerd.
  • De locatie van het matrixelement dat door de indexexpressies wordt gegeven, wordt berekend en deze locatie wordt het resultaat van de matrixtoegang.

12.8.12.3 Indexeerfunctietoegang

Voor toegang tot een indexeerfunctie moet de primary_no_array_creation_expression van de element_access een variabele of waarde van een klasse, struct of interfacetype zijn en dit type een of meer indexeerfuncties implementeren die van toepassing zijn op de argument_list van de element_access.

De bindingstijdverwerking van een indexeerfunctietoegang van de vorm P[A], waarbij P een primary_no_array_creation_expression is van een klasse, struct of interfacetype T, en A een argument_listis, bestaat uit de volgende stappen:

  • De set van indexeerfuncties die door T wordt geleverd, is samengesteld. De set bestaat uit alle indexeerfuncties die zijn gedeclareerd in T of een basistype van T die geen declaraties overschrijven en die toegankelijk zijn in de huidige context (§7,5).
  • De set wordt gereduceerd tot de indexeerfuncties die van toepassing zijn en niet worden verborgen door andere indexeerfuncties. De volgende regels worden toegepast op elke indexeerfunctie S.I in de set, waarbij S het type is waarin de indexeerfunctie I wordt gedeclareerd:
    • Als I niet van toepassing is op A (§12.6.4.2), wordt I uit de set verwijderd.
    • Indien I van toepassing is op A (§12.6.4.2), worden alle indexeerfuncties die zijn gedeclareerd in een basistype van S uit de set verwijderd.
    • Indien I van toepassing is op A (§12.6.4.2) en S een ander klassetype dan objectis, worden alle indexeerfuncties die in een interface zijn gedeclareerd, uit de set verwijderd.
  • Als de resulterende set kandidaat-indexeerfuncties leeg is, bestaan er geen toepasselijke indexeerfuncties en treedt er een bindingstijdfout op.
  • De beste indexeerfunctie van de set kandidaat-indexeerfuncties wordt geïdentificeerd met behulp van de overbelastingsoplossingsregels van §12.6.4. Als één beste indexeerfunctie niet kan worden geïdentificeerd, is de toegang van de indexeerfunctie niet eenduidig en treedt er een bindingstijdfout op.
  • De indexexpressies van de argument_list worden op volgorde geëvalueerd, van links naar rechts. Het resultaat van het verwerken van de toegang tot de indexeerder is een expressie die als indexeerdertoegang wordt geclassificeerd. De toegangsexpressie van de indexeerfunctie verwijst naar de indexeerfunctie die is bepaald in de bovenstaande stap en heeft een bijbehorende exemplaarexpressie van P en een bijbehorende argumentenlijst van Aen een gekoppeld type dat het type indexeerfunctie is. Als T een klassetype is, wordt het bijbehorende type gekozen uit de eerste declaratie of onderdrukking van de indexeerfunctie die is gevonden bij het starten met T en het doorzoeken van de basisklassen.

Afhankelijk van de context waarin het wordt gebruikt, zorgt een indexertoegang voor de aanroep van de get-accessor of de set-accessor van de indexer. Als de toegang tot de indexeerfunctie het doel is van een toewijzing, wordt de settoegangsfunctie aangeroepen om een nieuwe waarde toe te wijzen (§12.21.2). In alle andere gevallen wordt de get accessor aangeroepen om de huidige waarde te verkrijgen (§12.2.2).

12.8.13 Voorwaardelijke toegang tot null-elementen

Een null_conditional_element_access bestaat uit een primary_no_array_creation_expression, gevolgd door de twee tokens "?" en "[", dan gevolgd door een argument_list, vervolgens een "]" token, en daarna nul of meer dependent_access, die elk vooraf kunnen worden gegaan door een null_forgiving_operator.

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

Een null_conditional_element_access is een voorwaardelijke versie van element_access (§12.8.12) en het is een bindingstijdfout als het resultaattype is void. Zie voor een null-voorwaardelijke expressie waarin het resultaattype kan worden void (§12.8.11).

Een null_conditional_element_access expressie E heeft de vorm P?[A]B; waarbij B de dependent_accesszijn, indien aanwezig. De betekenis van E wordt als volgt bepaald:

  • Als het type P een null-waardetype is:

    Laat T het type expressie P.Value[A]Bzijn.

    • Als T een typeparameter is waarvan niet bekend is of het een verwijzingstype of een waarde-type dat niet null is, optreedt er een fout tijdens de compilatietijd.

    • Als T een niet-null-waardetype is, wordt het type ET?en is de betekenis van E hetzelfde als de betekenis van:

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

      Behalve dat P slechts één keer wordt geëvalueerd.

    • Anders is het type ETen is de betekenis van E hetzelfde als de betekenis van:

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

      Behalve dat P slechts één keer wordt geëvalueerd.

  • Anders:

    Laat T het type expressie P[A]Bzijn.

    • Als T een typeparameter is waarvan niet bekend is of het een verwijzingstype of een waarde-type dat niet null is, optreedt er een fout tijdens de compilatietijd.

    • Als T een niet-null-waardetype is, wordt het type ET?en is de betekenis van E hetzelfde als de betekenis van:

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

      Behalve dat P slechts één keer wordt geëvalueerd.

    • Anders is het type ETen is de betekenis van E hetzelfde als de betekenis van:

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

      Behalve dat P slechts één keer wordt geëvalueerd.

Opmerking: In een expressie van het formulier:

P?[A₀]?[A₁]

Als P evalueert tot null, worden noch A₀, noch A₁ geëvalueerd. Hetzelfde geldt als een expressie een reeks null_conditional_element_access of null_conditional_member_access§12.8.8 bewerkingen is.

eindnotitie

12.8.14 Deze toegang

Een this_access bestaat uit het trefwoord this.

this_access
    : 'this'
    ;

Een this_access is alleen toegestaan in het blok van een instantieconstructor, een instantiemethode, een exemplaartoegangsfunctie (§12.2.1) of een finalizer. Het heeft een van de volgende betekenissen:

  • Wanneer this wordt gebruikt in een primary_expression binnen een instantieconstructor van een klasse, wordt deze geclassificeerd als een waarde. Het type van de waarde is het exemplaartype (§15.3.2) van de klasse waarin het gebruik plaatsvindt en de waarde is een verwijzing naar het object dat wordt samengesteld.
  • Wanneer this wordt gebruikt in een primary_expression binnen een instantiemethode of instantietoegangsmiddel van een klasse, wordt deze geclassificeerd als een waarde. Het type van de waarde is het exemplaartype (§15.3.2) van de klasse waarin het gebruik plaatsvindt en de waarde is een verwijzing naar het object waarvoor de methode of accessor is aangeroepen.
  • Wanneer this wordt gebruikt in een primary_expression binnen een instantieconstructor van een struct, wordt deze geclassificeerd als een variabele. Het type van de variabele is het exemplaartype (§15.3.2) van de struct waarin het gebruik plaatsvindt en de variabele vertegenwoordigt de struct die wordt samengesteld.
    • Als de constructordeclaratie geen constructor-initialisatiefunctie heeft, gedraagt de this variabele zich precies hetzelfde als een uitvoerparameter van het structtype. Dit betekent met name dat de variabele zeker in elk uitvoeringspad van de instantieconstructor moet worden toegewezen.
    • Anders gedraagt de this-variabele zich precies hetzelfde als een ref parameter van het structtype. Dit betekent met name dat de variabele in eerste instantie wordt beschouwd als toegewezen.
  • Wanneer this wordt gebruikt in een primary_expression binnen een instantiemethode of instantietoegangsmiddel van een struct, wordt deze geclassificeerd als een variabele. Het type variabele is het exemplaartype (§15.3.2) van de struct waarin het gebruik plaatsvindt.
    • Als de methode of accessor geen iterator is (§15.14) of asynchrone functie (§15.15), vertegenwoordigt de this variabele de struct waarvoor de methode of accessor is aangeroepen.
      • Als de struct een readonly structis, gedraagt de this variabele zich precies hetzelfde als een invoerparameter van het structtype
      • Anders gedraagt de this-variabele zich precies hetzelfde als een ref parameter van het structtype
    • Als de methode of accessor een iterator- of asynchrone functie is, vertegenwoordigt de this variabele een kopie van de struct waarvoor de methode of accessor is aangeroepen en gedraagt zich precies hetzelfde als een waarde parameter van het structtype.

Het gebruik van this in een primary_expression in een andere context dan de hierboven genoemde is een compilatiefout. Het is met name niet mogelijk om te verwijzen naar this in een statische methode, een accessor voor statische eigenschappen of in een variable_initializer van een velddeclaratie.

12.8.15 Basistoegang

Een base_access bestaat uit het trefwoord base gevolgd door een token ".", een identificator en een optionele type_argument_list, of een argument_list tussen vierkante haken.

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

Een base_access wordt gebruikt voor toegang tot basisklasseleden die zijn verborgen door leden met vergelijkbare namen in de huidige klasse of structuur. Een base_access is alleen toegestaan in de hoofdtekst van een constructor van een instantie, een methode van een instantie, een toegangsfunctie van een instantie (§12.2.1) of een finalizer. Wanneer base.I plaatsvindt in een klasse of struct, geeft I een lid van de basisklasse van die klasse of struct aan. Wanneer base[E] plaatsvindt in een klasse, moet een toepasselijke indexeerfunctie ook aanwezig zijn in de basisklasse.

Tijdens de bindingstijd worden base_access expressies van de vorm base.I en base[E] exact geëvalueerd alsof ze zijn geschreven ((B)this).I en ((B)this)[E], waarbij B de basisklasse is van de klasse of struct waarin de constructie plaatsvindt. Daarom komen base.I en base[E] overeen met this.I en this[E], behalve this wordt weergegeven als een exemplaar van de basisklasse.

Wanneer een base_access verwijst naar een lid van een virtuele functie (een methode, eigenschap of indexeerfunctie), wordt de bepaling van welk functielid tijdens runtime moet worden aangeroepen (§12.6.6) gewijzigd. Het functielid dat wordt aangeroepen, wordt bepaald door de meest afgeleide implementatie (§15.6.4) van het functielid te vinden met betrekking tot B (in plaats van het uitvoeringstype van this, zoals gebruikelijk in een niet-basistoegang). Zo kan binnen een overschrijving van een virtuele functielid een base_access worden gebruikt om de overgenomen implementatie van het functielid aan te roepen. Als het functielid waarnaar wordt verwezen door een base_access abstract is, treedt er een bindingstijdfout op.

Opmerking: in tegenstelling tot thisis base geen expressie op zichzelf. Het is een trefwoord dat alleen wordt gebruikt in de context van een base_access of een constructor_initializer (§15.11.2). eindnotitie

12.8.16 Postfix incrementeer- en decrementeeroperators

post_increment_expression
    : primary_expression '++'
    ;

post_decrement_expression
    : primary_expression '--'
    ;

De operand van een bewerking voor het verhogen of verlagen van een voorvoegsel moet een expressie zijn die is geclassificeerd als een variabele, een eigenschapstoegang of een indexeerfunctietoegang. Het resultaat van de bewerking is een waarde van hetzelfde type als de operand.

Als het primary_expression het type compileertijd heeft dynamic, is de operator dynamisch gebonden (§12.3.3), heeft de post_increment_expression of post_decrement_expression het type compileertijd dynamic en worden de volgende regels toegepast tijdens runtime met behulp van het uitvoeringstype van de primary_expression.

Als de operand van een bewerking voor het verhogen of verlagen van een postfix een eigenschaps- of indexeerfunctietoegang is, moet de eigenschap of indexeerfunctie zowel een get- als een settoegangsfunctie hebben. Als dit niet het geval is, treedt er een bindingstijdfout op.

De oplossing voor overbelasting van unaire operatoren (§12.4.4) wordt toegepast om een specifieke operator-implementatie te selecteren. Vooraf gedefinieerde ++- en -- operators bestaan voor de volgende typen: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimalen een opsommingstype. De vooraf gedefinieerde ++ operators retourneren de waarde die wordt geproduceerd door 1 toe te voegen aan de operand en de vooraf gedefinieerde -- operators retourneren de waarde die wordt geproduceerd door 1 af te trekken van de operand. Als in een gecontroleerde context het resultaat van deze optel- of aftrekking buiten het bereik van het resultaattype valt en het resultaattype een integraal type of opsommingstype is, wordt er een System.OverflowException gegenereerd.

Er moet een impliciete conversie zijn van het retourtype van de geselecteerde unaire operator naar het type van de primary_expression, anders treedt er een compilatietijdfout op.

De uitvoeringstijdverwerking van een postfix-increment- of -decrementbewerking van de vorm x++ of x-- bestaat uit de volgende stappen:

  • Als x is geclassificeerd als een variabele:
    • x wordt geëvalueerd om de variabele te produceren.
    • De waarde van x wordt opgeslagen.
    • De opgeslagen waarde van x wordt geconverteerd naar het operandtype van de geselecteerde operator en de operator wordt aangeroepen met deze waarde als argument.
    • De waarde die door de operator wordt geretourneerd, wordt geconverteerd naar het type x en opgeslagen op de locatie die is opgegeven door de eerdere evaluatie van x.
    • De opgeslagen waarde van x wordt het resultaat van de bewerking.
  • Als x is geclassificeerd als een eigenschap of indexertoegang:
    • De exemplaarexpressie (als x niet staticis) en de lijst met argumenten (als x een indexeerfunctietoegang is) die aan x is gekoppeld, worden geëvalueerd en de resultaten worden gebruikt in de daaropvolgende aanroepen van de get- en set-toegangsmethodes.
    • De get accessor van x wordt aangeroepen en de geretourneerde waarde wordt opgeslagen.
    • De opgeslagen waarde van x wordt geconverteerd naar het operandtype van de geselecteerde operator en de operator wordt aangeroepen met deze waarde als argument.
    • De waarde die door de operator wordt geretourneerd, wordt geconverteerd naar het type x en de set accessor van x wordt aangeroepen met deze waarde als waardeargument.
    • De opgeslagen waarde van x wordt het resultaat van de bewerking.

De operators ++ en -- ondersteunen ook voorvoegsel notatie (§12.9.6). Het resultaat van x++ of x-- is de waarde van xvóór de bewerking, terwijl het resultaat van ++x of --x de waarde van xis na de bewerking. In beide gevallen heeft x zelf dezelfde waarde na de bewerking.

Een operator ++- of operator ---implementatie kan worden aangeroepen met behulp van een achtervoegsel- of voorvoegselnotatie. Het is niet mogelijk om afzonderlijke operator-implementaties te hebben voor de twee notaties.

12.8.17 De nieuwe operator

12.8.17.1 Algemeen

De operator new wordt gebruikt om nieuwe exemplaren van typen te maken.

Er zijn drie vormen van nieuwe expressies:

  • Expressies voor het maken van objecten en anonieme expressies voor het maken van objecten worden gebruikt om nieuwe exemplaren van klassetypen en waardetypen te maken.
  • Expressies voor het maken van matrices worden gebruikt om nieuwe exemplaren van matrixtypen te maken.
  • Expressies voor het maken van delegates worden gebruikt om instanties van delegate-typen te creëren.

De operator new impliceert het maken van een exemplaar van een type, maar impliceert niet noodzakelijkerwijs toewijzing van geheugen. In het bijzonder vereisen exemplaren van waardetypen geen extra geheugen buiten de variabelen waarin ze zich bevinden en vinden er geen toewijzingen plaats wanneer new wordt gebruikt om instanties van waardetypen te maken.

Opmerking: expressies voor het maken van gedelegeerden maken niet altijd nieuwe instanties. Wanneer de expressie op dezelfde manier wordt verwerkt als een methodegroepconversie (§10,8) of een anonieme functieconversie (§10,7) kan dit ertoe leiden dat een bestaand gemachtigde exemplaar opnieuw wordt gebruikt. eindnotitie

12.8.17.2 Expressies voor het maken van objecten

Een object_creation_expression wordt gebruikt om een nieuw exemplaar van een class_type of een value_typete maken.

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
    ;

Het type van een object_creation_expression is een class_type, een value_typeof een type_parameter. Het type kan geen tuple_type of een abstracte of statische class_typezijn.

De optionele argument_list (§12.6.2) is alleen toegestaan als het type een class_type of een struct_typeis.

Een expressie voor het maken van objecten kan de lijst met constructorargumenten weglaten en haakjes plaatsen, mits deze een object-initialisatiefunctie of verzamelingsinitiizer bevat. Het weglaten van de lijst met constructorargumenten en het insluiten van haakjes is gelijk aan het opgeven van een lege argumentenlijst.

Verwerking van een expressie voor het maken van objecten die een object initialisatie of verzamelings initialisatie bevat, bestaat uit de eerste verwerking van de instantieconstructor en vervolgens de initialisaties van het lid of element die zijn opgegeven door de object initialisatie (§12.8.17.17.3) of de initialisatie van verzamelingen (§12.8.17.4).

Als een van de argumenten in de optionele argument_list het type compileertijd heeft dynamic, is de object_creation_expression dynamisch gebonden (§12.3.3) en worden de volgende regels toegepast tijdens runtime met behulp van het runtimetype van deze argumenten van de argument_list met het type compileertijd dynamic. Het maken van het object ondergaat echter een beperkte controle van de compilatietijd, zoals beschreven in §12.6.5.

De bindingstijdverwerking van een object_creation_expression van het formulier new T(A), waarbij T een class_typeof een value_typeis, en A een optionele argument_listis, bestaat uit de volgende stappen:

  • Als T een value_type is en A niet aanwezig is:
    • De object_creation_expression is een standaardconstructor-aanroep. Het resultaat van de object_creation_expression is een waarde van het type T, namelijk de standaardwaarde voor T zoals gedefinieerd in §8.3.3.
  • Anders, als T een type_parameter is en A niet aanwezig is:
    • Als er geen waardetypebeperking of constructorbeperking (§15.2.5) is opgegeven voor T, treedt er een bindingstijdfout op.
    • Het resultaat van de object_creation_expression is een waarde van het runtimetype waaraan de typeparameter is gebonden, namelijk het resultaat van het aanroepen van de standaardconstructor van dat type. Het runtimetype kan een verwijzingstype of een waardetype zijn.
  • Anders, als T een class_type of een struct_typeis:
    • Als T een abstracte of statische class_typeis, treedt er een compilatietijdfout op.
    • De instantieconstructor die moet worden aangeroepen, wordt bepaald met behulp van de regels voor overbelastingsoplossing van §12.6.4. De set kandidaat-exemplaarconstructors bestaat uit alle toegankelijke exemplaarconstructors die zijn gedeclareerd in T, die van toepassing zijn op A (§12.6.4.2). Als de set kandidaat-exemplaarconstructors leeg is of als één beste exemplaarconstructor niet kan worden geïdentificeerd, treedt er een bindingstijdfout op.
    • Het resultaat van de object_creation_expression is een waarde van het type T, namelijk de waarde die wordt geproduceerd door de instantieconstructor aan te roepen die in de bovenstaande stap is bepaald.
    • Anders is de object_creation_expression ongeldig en treedt er een bindingstijdfout op.

Zelfs als de object_creation_expression dynamisch is gebonden, is het type compileertijd nog steeds T.

De runtimeverwerking van een object_creation_expression van de vorm new T(A), waarbij T een class_type of een struct_type is en A een optionele argument_listis, bestaat uit de volgende stappen:

  • Als T een class_typeis:
    • Er wordt een nieuw exemplaar van klasse T toegewezen. Als er onvoldoende geheugen beschikbaar is om het nieuwe exemplaar toe te wijzen, wordt er een System.OutOfMemoryException gegenereerd en worden er geen verdere stappen uitgevoerd.
    • Alle velden van het nieuwe exemplaar worden geïnitialiseerd tot de standaardwaarden (§9,3).
    • De instantieconstructor wordt aangeroepen volgens de regels van het aanroepen van functieleden (§12.6.6). Een verwijzing naar het zojuist toegewezen exemplaar wordt automatisch doorgegeven aan de instantieconstructor en het exemplaar kan als zodanig worden geopend vanuit die constructor.
  • Als T een struct_typeis:
    • Een exemplaar van het type T wordt gemaakt door een tijdelijke lokale variabele toe te wijzen. Aangezien een instantieconstructor van een struct_type is vereist om zeker een waarde toe te wijzen aan elk veld van het exemplaar dat wordt gemaakt, is er geen initialisatie van de tijdelijke variabele nodig.
    • De instantieconstructor wordt aangeroepen volgens de regels van het aanroepen van functieleden (§12.6.6). Een verwijzing naar het zojuist toegewezen exemplaar wordt automatisch doorgegeven aan de instantieconstructor en het exemplaar kan als zodanig worden geopend vanuit die constructor.

12.8.17.3 Object-initialisatoren

Een object initialisatie geeft waarden op voor nul of meer velden, eigenschappen of geïndexeerde elementen van een object.

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
    ;

Een object-initialisator bestaat uit een reeks lid-initialisatoren, tussen { en } tokens en gescheiden door komma's. Elke member_initializer wijst een doel aan voor de initialisatie. Een identifier duidt op een toegankelijk veld of een eigenschap van het object dat wordt geïnitialiseerd, terwijl een argument_list tussen vierkante haken de argumenten specificeert voor een toegankelijke indexeerfunctie. Het is een fout dat een object-initialisatiefunctie meer dan één lid initialisatiefunctie voor hetzelfde veld of dezelfde eigenschap bevat.

Opmerking: hoewel een object-initialisatiefunctie niet meer dan één keer hetzelfde veld of dezelfde eigenschap mag instellen, zijn er geen dergelijke beperkingen voor indexeerfuncties. Een object-initialisatiefunctie kan meerdere initialisatiedoelen bevatten die verwijzen naar indexeerfuncties en kunnen zelfs meerdere keren dezelfde indexeerfunctieargumenten gebruiken. eindnotitie

Elke initializer_target wordt gevolgd door een gelijkteken en een expressie, een object-initialisatiefunctie of een verzamelings-initialisatiefunctie. Het is niet mogelijk voor expressies in de object-initialisatiefunctie om te verwijzen naar het zojuist gemaakte object dat het initialiseert.

Een lid-initialisator die een expressie na het gelijkteken specificeert, wordt op dezelfde manier verwerkt als een toewijzing (§12.21.2) op het doel.

Een lid-initialisatie die een object-initialisatie specificeert na het gelijkteken, is een geneste object-initialisatie, d.w.z. een initialisatie van een ingesloten object. In plaats van een nieuwe waarde toe te wijzen aan het veld of de eigenschap, worden de toewijzingen in de geneste object-initialisatiefunctie behandeld als toewijzingen aan leden van het veld of de eigenschap. Geneste object-initialisatiefuncties kunnen niet worden toegepast op eigenschappen met een waardetype of op alleen-lezenvelden met een waardetype.

Een lidinitialisator die na het gelijkteken een verzamelingsinitialisator specificeert, is een initialisatie van een ingesloten verzameling. In plaats van een nieuwe verzameling toe te wijzen aan het doelveld, de eigenschap of de indexeerfunctie, worden de elementen in de initialisatiefunctie toegevoegd aan de verzameling waarnaar wordt verwezen door het doel. Het doelobject moet van een verzamelingstype zijn dat voldoet aan de eisen gespecificeerd in §12.8.17.4.

Wanneer een initialisatiedoel verwijst naar een indexeerfunctie, worden de argumenten voor de indexeerfunctie altijd één keer geëvalueerd. Dus zelfs als de argumenten nooit worden gebruikt (bijvoorbeeld vanwege een lege geneste initialisatiefunctie), worden ze geëvalueerd op hun bijwerkingen.

Voorbeeld: De volgende klasse vertegenwoordigt een punt met twee coördinaten:

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

Een exemplaar van Point kan als volgt worden gemaakt en geïnitialiseerd:

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

Dit heeft hetzelfde effect als

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

waarbij __a een onzichtbare en ontoegankelijke tijdelijke variabele is.

In de volgende klasse ziet u een rechthoek die is gemaakt op basis van twee punten en het maken en initialiseren van een Rectangle-exemplaar:

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

Een exemplaar van Rectangle kan als volgt worden gemaakt en geïnitialiseerd:

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

Dit heeft hetzelfde effect als

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;

waarbij __r, __p1 en __p2 tijdelijke variabelen zijn die anders onzichtbaar en niet toegankelijk zijn.

Als de constructor van Rectanglede twee ingesloten Point exemplaren toewijst, kunnen ze worden gebruikt om de ingesloten Point exemplaren te initialiseren, in plaats van nieuwe toe te wijzen.

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

de volgende constructie kan worden gebruikt om de ingesloten Point exemplaren te initialiseren in plaats van nieuwe exemplaren toe te wijzen:

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

Dit heeft hetzelfde effect als

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

einde voorbeeld

12.8.17.4 Initializers voor verzameling

Met een initialisatiefunctie voor verzamelingen worden de elementen van een verzameling opgegeven.

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
    ;

Een verzamelingsinitializer bestaat uit een reeks elementinitialisaties, tussen { en } tokens en gescheiden door komma's. Met elke element-initialisatiefunctie wordt een element opgegeven dat moet worden toegevoegd aan het verzamelingsobject dat wordt geïnitialiseerd en bestaat uit een lijst met expressies tussen { en } tokens en gescheiden door komma's. Een initialisatiefunctie voor één expressie-element kan zonder accolades worden geschreven, maar kan geen toewijzingsexpressie zijn om dubbelzinnigheid met initialisatie van leden te voorkomen. De non_assignment_expression productie wordt gedefinieerd in §12.22.

voorbeeld: hieronder ziet u een voorbeeld van een expressie voor het maken van objecten die een initialisatiefunctie voor verzamelingen bevat:

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

einde voorbeeld

Het verzamelingsobject waarop een verzamelingsinitializer wordt toegepast, moet van een type zijn dat System.Collections.IEnumerable implementeert, anders volgt er een compile-time fout. Voor elk opgegeven element in volgorde van links naar rechts wordt normale opzoekactie toegepast om een lid met de naam Addte vinden. Als het resultaat van het opzoeken van leden geen methodegroep is, treedt er een compilatietijdfout op. Anders wordt overbelastingsresolutie toegepast met de expressielijst van de element-initialisatiefunctie als de argumentenlijst en roept de initialisatiefunctie voor de verzameling de resulterende methode aan. Het verzamelingsobject bevat dus een toepasselijke instantie of extensiemethode met de naam Add voor elke element-initialisatiefunctie.

Voorbeeld: Hieronder ziet u een klasse die een contactpersoon vertegenwoordigt met een naam en een lijst met telefoonnummers, en het maken en initialiseren van een 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" }
            }
        };
    }
}

dat hetzelfde effect heeft als

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;

waarbij __clist, __c1 en __c2 tijdelijke variabelen zijn die anders onzichtbaar en niet toegankelijk zijn.

einde voorbeeld

12.8.17.5 Expressies voor het maken van matrices

Een array_creation_expression wordt gebruikt om een nieuw exemplaar van een array_typete maken.

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

Met een expressie voor het aanmaken van een array in de eerste vorm wordt een arrayinstantie van het type toegewezen die het resultaat is van het verwijderen van elk van de afzonderlijke expressies uit de expressielijst.

voorbeeld: de expressie voor het maken van een array new int[10,20] produceert een arrayexemplaar van het type int[,], en de expressie voor het maken van een nieuwe array int[10][,] produceert een arrayexemplaar van het type int[][,]. einde voorbeeld

Elke expressie in de expressielijst moet van het type int, uint, longof ulongzijn of impliciet kunnen worden omgezet in een of meer van deze typen. De waarde van elke uitdrukking bepaalt de lengte van de bijbehorende dimensie in de nieuw toegewezen array-instantie. Omdat de lengte van een matrixdimensie niet-negatief is, is het een compilatiefout om een constante expressie met een negatieve waarde in de expressielijst te hebben.

Behalve in een onveilige context (§23.2), is de indeling van matrices niet opgegeven.

Als een expressie voor het maken van een matrix van het eerste formulier een initialisatiefunctie voor een matrix bevat, moet elke expressie in de expressielijst een constante zijn en moeten de rang- en dimensielengten die door de expressielijst zijn opgegeven, overeenkomen met die van de initialisatiefunctie van de matrix.

In een expressie voor het maken van een matrix van het tweede of derde formulier moet de rangorde van het opgegeven matrixtype of rangaanduiding overeenkomen met die van de initialisatiefunctie van de matrix. De afzonderlijke dimensielengten worden afgeleid van het aantal elementen in elk van de bijbehorende nestniveaus van de matrix-initialisatiefunctie. De initialisatie-expressie in de volgende declaratie

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

komt precies overeen met

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

Een expressie voor het maken van een matrix van het derde formulier wordt een impliciet getypte expressie voor het maken van matricesgenoemd. Het is vergelijkbaar met het tweede formulier, behalve dat het elementtype van de matrix niet expliciet wordt opgegeven, maar wordt bepaald als het beste gemeenschappelijke type (§12.6.3.15) van de set expressies in de matrix-initialisatiefunctie. Voor een multidimensionale matrix, d.w.z. een matrix waarin de rank_specifier ten minste één komma bevat, bestaat deze set uit alle expressiesdie zijn gevonden in geneste array_initializers.

Matrix initializers worden verder beschreven in §17.7.

Het resultaat van het evalueren van een expressie voor het aanmaken van een array wordt beschouwd als een waarde, namelijk een verwijzing naar het zojuist toegewezen array-exemplaar. De looptijdverwerking van een expressie voor het maken van een matrix bestaat uit de volgende stappen:

  • De dimensielengte-expressies van de expression_list worden op volgorde geëvalueerd, van links naar rechts. Na de evaluatie van elke expressie wordt een impliciete conversie (§10,2) uitgevoerd op een van de volgende typen: int, uint, long, ulong. Het eerste type in deze lijst waarvoor een impliciete conversie bestaat, wordt gekozen. Als de evaluatie van een expressie of de daaropvolgende impliciete conversie een uitzondering veroorzaakt, worden er geen verdere expressies geëvalueerd en worden er geen verdere stappen uitgevoerd.
  • De berekende waarden voor de dimensielengten worden als volgt gevalideerd: Als een of meer van de waarden kleiner zijn dan nul, wordt er een System.OverflowException gegenereerd en worden er geen verdere stappen uitgevoerd.
  • Een array-exemplaar met de opgegeven dimensielengten wordt toegewezen. Als er onvoldoende geheugen beschikbaar is om het nieuwe exemplaar toe te wijzen, wordt er een System.OutOfMemoryException gegenereerd en worden er geen verdere stappen uitgevoerd.
  • Alle elementen van de nieuwe arrayinstanties worden geïnitialiseerd met hun standaardwaarden (§9.3).
  • Als de expressie voor het maken van de matrix een initialisatiefunctie voor matrices bevat, wordt elke expressie in de matrix-initialisatie geëvalueerd en toegewezen aan het bijbehorende matrixelement. De evaluaties en toewijzingen worden uitgevoerd in de volgorde waarin de expressies worden geschreven in de initialisatiefunctie van de matrix, met andere woorden: elementen worden geïnitialiseerd in toenemende indexvolgorde, waarbij de meest rechtse dimensie eerst toeneemt. Als de evaluatie van een bepaalde expressie of de volgende toewijzing aan het bijbehorende matrixelement een uitzondering veroorzaakt, worden er geen verdere elementen geïnitialiseerd (en de resterende elementen hebben dus hun standaardwaarden).

Met een expressie voor het maken van een matrix kan een matrix worden geïnstantieerd met elementen van een matrixtype, maar de elementen van een dergelijke matrix moeten handmatig worden geïnitialiseerd.

Voorbeeld: De verklaring

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

maakt een eendimensionale matrix met 100 elementen van het type int[]. De initiële waarde van elk element is null. Het is niet mogelijk dat dezelfde expressie voor het maken van een matrix ook de submatrices instantiëren en de instructie

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

resulteert in een compilatietijdfout. Instantiëring van de submatrices kan in plaats daarvan handmatig worden uitgevoerd, zoals in

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

einde voorbeeld

Opmerking: Wanneer een array van arrays een "rechthoekige" vorm heeft, dat wil zeggen wanneer de sub-arrays allemaal dezelfde lengte hebben, is het efficiënter om een multidimensionale array te gebruiken. In het bovenstaande voorbeeld maakt instantiëring van de matrix van matrices 101 objecten: één buitenste matrix en 100 submatrices. Daarentegen,

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

maakt slechts één object, een tweedimensionale matrix en bereikt de toewijzing in één instructie.

eindnotitie

voorbeeld van: Hieronder ziet u voorbeelden van impliciet getypte expressies voor het maken van matrices:

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

De laatste expressie veroorzaakt een compilatiefout omdat noch int noch string impliciet converteerbaar is naar de andere, en er is dus geen best gemeenschappelijk type. In dit geval moet een expliciet getypte expressie voor het maken van een array worden gebruikt, bijvoorbeeld door het type object[]op te geven. U kunt ook een van de elementen casten naar een gemeenschappelijk basistype, dat vervolgens het uitgestelde elementtype wordt.

einde voorbeeld

Impliciet getypte expressies voor het maken van matrices kunnen worden gecombineerd met anonieme object initialisatiefuncties (§12.8.17.7) om anoniem getypte gegevensstructuren te maken.

voorbeeld van:

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

einde voorbeeld

12.8.17.6 Aanmaakexpressies voor gedelegeerden

Een delegate_creation_expression wordt gebruikt om een exemplaar van een delegate_typete verkrijgen.

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

Het argument van een expressie voor het creëren van gedelegeerden moet een methodegroep, een anonieme functie, of een waarde van het compileertijdtype dynamic of een delegate_typezijn. Als het argument een methodegroep is, identificeert het de methode en, voor een instantiemethode, het object waarvoor een gemachtigde moet worden gemaakt. Als het argument een anonieme functie is, definieert het rechtstreeks de parameters en de hoofdtekst van de methode van het gemachtigde doel. Als het argument een waarde is, wordt een gemachtigde instantie geïdentificeerd waarvan een kopie moet worden gemaakt.

Als de expressie het type compileertijd heeft dynamic, wordt de delegate_creation_expression dynamisch gebonden (§12.8.17.6) en worden de onderstaande regels toegepast tijdens runtime met behulp van het uitvoeringstype van de -expressie. Anders worden de regels tijdens het compileren toegepast.

De bindingstijdverwerking van een delegate_creation_expression van de vorm nieuw D(E), waarbij D een delegate_type is en E een expressieis, bestaat uit de volgende stappen:

  • Als E een methodegroep is, wordt de expressie voor het maken van gemachtigden op dezelfde manier verwerkt als een methodegroepconversie (§10,8) van E tot D.

  • Als E een anonieme functie is, wordt de expressie voor het maken van gemachtigden op dezelfde manier verwerkt als een anonieme functieconversie (§10,7) van E tot D.

  • Als E een waarde is, moet E compatibel zijn (§20.2) met D, en het resultaat is een verwijzing naar een zojuist gemaakte gemachtigde met een aanroeplijst met één vermelding die Eaanroept.

De runtimeverwerking van een delegate_creation_expression van de vorm new D(E), waarbij D een delegate_type is en E een expressieis, bestaat uit de volgende stappen:

  • Als E een methodegroep is, wordt de expressie voor het maken van gemachtigden geëvalueerd als een methodegroepconversie (§10,8) van E naar D.
  • Als E een anonieme functie is, wordt de creatie van de delegeer geëvalueerd als een omzetting van anonieme functie van E naar D (§10.7).
  • Als E een waarde van een delegate_typeis:
    • E wordt geëvalueerd. Als deze evaluatie een uitzondering veroorzaakt, worden er geen verdere stappen uitgevoerd.
    • Als de waarde van E is null, wordt er een System.NullReferenceException gegenereerd en worden er geen verdere stappen uitgevoerd.
    • Er wordt een nieuw exemplaar van het delegate-type D toegewezen. Als er onvoldoende geheugen beschikbaar is om het nieuwe exemplaar toe te wijzen, wordt er een System.OutOfMemoryException gegenereerd en worden er geen verdere stappen uitgevoerd.
    • Het nieuwe gemachtigde exemplaar wordt geïnitialiseerd met een aanroeplijst met één vermelding die Eaanroept.

De aanroeplijst van een gemachtigde wordt bepaald wanneer de gemachtigde wordt geïnstantieerd en blijft vervolgens constant gedurende de gehele levensduur van de gemachtigde. Met andere woorden, het is niet mogelijk om de aanroepbare doelobjecten van een delegate te wijzigen zodra deze gemaakt is.

Opmerking: Onthoud dat wanneer twee gedelegeerden worden gecombineerd of een van de andere wordt verwijderd, er een nieuwe gedelegeerde ontstaat; de inhoud van geen bestaande gedelegeerde verandert. eindnotitie

Het is niet mogelijk om een gemachtigde te maken die verwijst naar een eigenschap, indexeerfunctie, door de gebruiker gedefinieerde operator, instantieconstructor, finalizer of statische constructor.

Voorbeeld: Zoals hierboven beschreven, wordt bij het maken van een delegate van een methodegroep de parameterlijst en het retourtype van de delegate bepaald welke van de overbelaste methoden worden geselecteerd. In het voorbeeld

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

het A.f veld wordt geïnitialiseerd met een gemachtigde die verwijst naar de tweede Square methode, omdat die methode exact overeenkomt met de parameterlijst en het retourtype van DoubleFunc. Als de tweede Square methode niet aanwezig was, zou er een compilatietijdfout zijn opgetreden.

einde voorbeeld

12.8.17.7 Anonieme expressies voor het maken van objecten

Een anonymous_object_creation_expression wordt gebruikt om een object van een anoniem type te maken.

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
    ;

Een anonieme object-initialisatiefunctie declareert een anoniem type en retourneert een exemplaar van dat type. Een anoniem type is een naamloos klassetype dat rechtstreeks wordt overgenomen van object. De leden van een anoniem type zijn een reeks alleen-lezen eigenschappen die zijn afgeleid van de anonieme object-initialisatiefunctie die wordt gebruikt om een exemplaar van het type te maken. Een anonieme object-initialisatiefunctie van het formulier

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

declareert een anoniem type van het formulier

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

waarbij elke «Tx» het type is van de bijbehorende expressie «ex». De expressie die in een member_declarator wordt gebruikt, heeft een type. Het is een compilatiefout dat een uitdrukking in een member_declaratornull of een anonieme functie is.

De namen van een anoniem type en van de parameter aan de Equals methode worden automatisch gegenereerd door de compiler en kunnen niet worden verwezen in programmatekst.

Binnen hetzelfde programma worden twee anonieme object-initialisatiefuncties die een reeks eigenschappen van dezelfde namen en compileertijdtypen in dezelfde volgorde opgeven, exemplaren van hetzelfde anonieme type produceren.

Voorbeeld: In het voorbeeld

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

de toewijzing op de laatste regel is toegestaan omdat p1 en p2 van hetzelfde anonieme type zijn.

einde voorbeeld

De methoden Equals en GetHashcode voor anonieme typen overschrijven de methoden die zijn overgenomen van objecten worden gedefinieerd in termen van de Equals en GetHashcode van de eigenschappen, zodat twee exemplaren van hetzelfde anonieme type gelijk zijn als en alleen als alle eigenschappen gelijk zijn.

Een liddeclaratie kan worden afgekort tot een eenvoudige naam (§12.8.4), een lidtoegang (§12.8.7), een null-conditionele projectie-initialisator (§12.8.8) of een basistoegang (§12.8.15). Dit wordt een projectie-initialisatie genoemd en is een afkorting voor een declaratie van en toewijzing aan een eigenschap met dezelfde naam. Met name liddeclaraties van de formulieren

«identifier», «expr» . «identifier» en «expr» ? . «identifier»

zijn exact gelijk aan respectievelijk het volgende:

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

In een projectie-initialisatiefunctie selecteert de id dus zowel de waarde als het veld of de eigenschap waaraan de waarde is toegewezen. Intuïtief projecteert een projectie initializer niet alleen een waarde, maar ook de naam van de waarde.

12.8.18 De typeof-operator

De operator typeof wordt gebruikt om het System.Type-object voor een type te verkrijgen.

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
    : ','
    ;

nl-NL: De eerste vorm van typeof_expression bestaat uit een typeof trefwoord gevolgd door een type tussen haakjes. Het resultaat van een expressie van dit formulier is het System.Type object voor het aangegeven type. Er is slechts één System.Type object voor een bepaald type. Dit betekent dat voor een type T, typeof(T) == typeof(T) altijd waar is. Het type mag niet dynamiczijn.

De tweede vorm van typeof_expression bestaat uit een typeof trefwoord gevolgd door een unbound_type_nametussen haakjes.

Opmerking: een unbound_type_name is vergelijkbaar met een type_name (§7.8), behalve dat een unbound_type_namegeneric_dimension_specifierbevat waarin een type_nametype_argument_listbevat. eindnotitie

Wanneer de operand van een typeof_expression een reeks tokens is die voldoet aan de grammatica van zowel unbound_type_name als type_name, namelijk wanneer deze geen generic_dimension_specifier of een type_argument_listbevat, wordt de reeks tokens beschouwd als een type_name. De betekenis van een unbound_type_name wordt als volgt bepaald:

  • Converteer de reeks tokens naar een type_name door elke generic_dimension_specifier te vervangen door een type_argument_list met hetzelfde aantal komma's en het trefwoord object als elke type_argument.
  • Evalueer de resulterende type_name, terwijl alle parameterbeperkingen van het type worden genegeerd.
  • De unbound_type_name wordt omgezet in het niet-afhankelijke algemene type dat is gekoppeld aan het resulterende samengestelde type (§8.4).

Het is een fout als de typenaam een nullable referentietype is.

Het resultaat van de typeof_expression is het System.Type object voor het resulterende ongebonden generieke type.

De derde vorm van typeof_expression bestaat uit een typeof trefwoord, gevolgd door een void trefwoord tussen haakjes. Het resultaat van een expressie van dit formulier is het System.Type object dat de afwezigheid van een type aangeeft. Het typeobject dat wordt geretourneerd door typeof(void) verschilt van het typeobject dat wordt geretourneerd voor elk type.

Opmerking: dit speciale System.Type-object is handig in klassebibliotheken die reflectie op methoden in de taal mogelijk maken, waarbij deze methoden een manier willen hebben om het retourtype van een methode weer te geven, inclusief void methoden, met een exemplaar van System.Type. eindnotitie

De operator typeof kan worden gebruikt voor een typeparameter. Dit is een compilatietijdfout als de naam van het type een null-verwijzingstype is. Het resultaat is het System.Type-object voor het runtimetype dat is gebonden aan de typeparameter. Als het runtimetype een nullable verwijzingstype is, is het resultaat het overeenkomstige niet-null-referentietype. De operator typeof kan ook worden gebruikt voor een samengesteld type of een niet-afhankelijk algemeen type (§8.4.4). Het System.Type-object voor een niet-afhankelijk algemeen type is niet hetzelfde als het System.Type object van het exemplaartype (§15.3.2). Het exemplaartype is altijd een gesloten geconstrueerd type tijdens runtime, zodat het System.Type object afhankelijk is van de argumenten van het runtimetype die in gebruik zijn. Het niet-afhankelijke algemene type heeft daarentegen geen typeargumenten en levert hetzelfde System.Type object op, ongeacht de argumenten van het runtimetype.

voorbeeld: het voorbeeld

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

produceert de volgende uitvoer:

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]

Houd er rekening mee dat int en System.Int32 hetzelfde type zijn. Het resultaat van typeof(X<>) is niet afhankelijk van het typeargument, maar het resultaat van typeof(X<T>) wel.

einde voorbeeld

12.8.19 De grootte van de operator

De operator sizeof retourneert het aantal 8-bits bytes dat wordt bezet door een variabele van een bepaald type. Het type dat is opgegeven als operand voor grootte moet een unmanaged_type zijn (§8,8).

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

Voor bepaalde vooraf gedefinieerde typen levert de operator sizeof een constante int waarde op, zoals wordt weergegeven in de onderstaande tabel:

Expressie- resultaat
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

Voor een enumtype Tis het resultaat van de expressie sizeof(T) een constante waarde is die gelijk is aan de grootte van het onderliggende type, zoals hierboven is aangegeven. Voor alle andere operanden wordt de operator sizeof opgegeven in §23.6.9.

12.8.20 De gecontroleerde en ongecontroleerde operators

De operators checked en unchecked worden gebruikt om de context voor overloopcontrole te beheren voor rekenkundige bewerkingen en conversies van integraal type.

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

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

De operator checked evalueert de ingesloten expressie in een gecontroleerde context en de operator unchecked evalueert de ingesloten expressie in een niet-gecontroleerde context. Een checked_expression of unchecked_expression komt precies overeen met een parenthesized_expression (§12.8.5), behalve dat de ingesloten expressie wordt geëvalueerd binnen de gespecificeerde context van overloopcontrole.

De context voor overloopcontrole kan ook worden beheerd via de checked- en unchecked-instructies (§13.12).

De volgende bewerkingen worden beïnvloed door de context van de overloopcontrole die is ingesteld door de gecontroleerde en niet-gecontroleerde operators en instructies:

  • De vooraf gedefinieerde operatoren ++ en -- (§12.8.16 en §12.9.6), wanneer de operand een integraal of opsommingstype heeft.
  • De vooraf gedefinieerde - unaire operator (§12.9.3), wanneer de operand van een integraal type is.
  • De vooraf gedefinieerde +, -, *en / binaire operatoren (§12.10), wanneer beide operanden van integrale of opsommingstypen zijn.
  • Expliciete numerieke conversies (§10.3.2) van het ene integraal- of enumtype naar een ander integraal of opsommingstype, of van float of double naar een integraal of opsommingstype.

Wanneer een van de bovenstaande bewerkingen een resultaat produceert dat te groot is om weer te geven in het doeltype, bepaalt de context waarin de bewerking wordt uitgevoerd het resulterende gedrag:

  • Als de bewerking in een checked context een constante expressie is (§12.23), treedt er een compilatietijdfout op. Anders, als de bewerking tijdens runtime wordt uitgevoerd, wordt er een System.OverflowException opgeworpen.
  • In een unchecked context wordt het resultaat afgekapt door alle bits in hoge volgorde te verwijderen die niet in het doeltype passen.

Voor niet-constante expressies (§12.23) (expressies die tijdens runtime worden geëvalueerd) die niet zijn ingesloten door checked of unchecked operators of instructies, wordt de standaardoverloopcontrolecontext uitgeschakeld, tenzij externe factoren (zoals compilerswitches en configuratie van uitvoeringsomgevingen) voor gecontroleerde evaluatie worden aangeroepen.

Voor constante expressies (§12.23) (expressies die tijdens het compileren volledig kunnen worden geëvalueerd), wordt de standaardoverloopcontrolecontext altijd gecontroleerd. Tenzij een constante expressie expliciet in een unchecked context wordt geplaatst, veroorzaakt overloop die optreedt tijdens de compilatietijd-evaluatie van de expressie altijd compilatietijdfouten.

De hoofdtekst van een anonieme functie wordt niet beïnvloed door checked of unchecked contexten waarin de anonieme functie plaatsvindt.

Voorbeeld: In de volgende code

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
}

er worden geen compilatiefouten gerapporteerd omdat geen van de expressies tijdens het compileren kan worden geëvalueerd. Tijdens runtime genereert de methode F een System.OverflowExceptionen retourneert de methode G –727379968 (de lagere 32 bits van het buitenbereikresultaat). Het gedrag van de methode H is afhankelijk van de standaardcontext voor overloopcontrole voor de compilatie, maar dit is hetzelfde als F of hetzelfde als G.

einde voorbeeld

Voorbeeld: In de volgende code

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 overlopen die optreden bij het evalueren van de constante uitdrukkingen in F en H leiden tot compilatietijdfouten omdat de uitdrukkingen worden geëvalueerd in een checked-context. Er treedt ook een overloop op bij het evalueren van de constante expressie in G, maar omdat de evaluatie plaatsvindt in een unchecked context, wordt de overloop niet gerapporteerd.

einde voorbeeld

De operators checked en unchecked beïnvloeden alleen de context van de overloopcontrole voor die bewerkingen die tekstueel zijn opgenomen binnen de tokens '(' en ')'. De operators hebben geen effect op functieleden die worden aangeroepen als gevolg van het evalueren van de ingesloten expressie.

Voorbeeld: In de volgende code

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

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

het gebruik van checked in F heeft geen invloed op de evaluatie van x * y in Multiply, dus x * y wordt geëvalueerd in de standaardoverloopcontrolecontext.

einde voorbeeld

De operator unchecked is handig bij het schrijven van constanten van de ondertekende integrale typen in hexadecimale notatie.

voorbeeld van:

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

Beide hexadecimale constanten hierboven zijn van het type uint. Omdat de constanten buiten het int-bereik vallen, zouden de casts naar unchecked compileertijdfouten produceren zonder de int-operator.

einde voorbeeld

Opmerking: de operators en instructies van de checked en unchecked stellen programmeurs in staat om bepaalde aspecten van sommige numerieke berekeningen te beheren. Het gedrag van sommige numerieke operators is echter afhankelijk van de gegevenstypen van hun operanden. Als u bijvoorbeeld twee decimalen vermenigvuldigt, resulteert dit altijd in een uitzondering op overloop, zelfs binnen een expliciet niet-gecontroleerd constructie. Op dezelfde manier resulteert het vermenigvuldigen van twee floats nooit in een uitzondering op overloop, zelfs binnen een expliciet gecontroleerde constructie. Bovendien worden andere operators nooit beïnvloed door de controlemodus, of dit nu standaard of expliciet is. eindnotitie

12.8.21 Standaardwaardeexpressies

Een standaardwaardeexpressie wordt gebruikt om de standaardwaarde (§9.3) van een type te verkrijgen.

default_value_expression
    : explictly_typed_default
    | default_literal
    ;

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

default_literal
    : 'default'
    ;

Een default_literal vertegenwoordigt een standaardwaarde (§9,3). Het heeft geen type, maar kan worden geconverteerd naar elk type via een standaard letterlijke conversie (§10.2.16).

Het resultaat van een default_value_expression is de standaardwaarde (§9,3) van het expliciete type in een explictly_typed_default, of het doeltype van de conversie voor een default_value_expression.

Een default_value_expression is een constante expressie (§12.23) als het type een van de volgende is:

  • een referentietype
  • een typeparameter die bekend staat als referentietype (§8.2);
  • een van de volgende waardetypen: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool,; of
  • elke opsommingstype.

12.8.22 Stacktoewijzing

Met een stackallocatie-expressie wordt een blok geheugen toegewezen vanuit de uitvoeringsstack. De uitvoeringsstack is een geheugengebied waarin lokale variabelen worden opgeslagen. De uitvoeringsstack maakt geen deel uit van de beheerde heap. Het geheugen dat wordt gebruikt voor lokale variabele opslag, wordt automatisch hersteld wanneer de huidige functie wordt geretourneerd.

De veilige contextregels voor een stacktoewijzingsexpressie worden beschreven in §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
    ;

De unmanaged_type (§8.8) geeft het type aan van de items die worden opgeslagen op de zojuist toegewezen locatie en de expressie het aantal van deze items aangeeft. Samen geven deze de vereiste toewijzingsgrootte op. Het type van de expressie kan impliciet worden omgezet naar het type int.

Omdat de grootte van een stacktoewijzing niet negatief kan zijn, is het een compilatiefout om het aantal items op te geven als een constant_expression die een negatieve waarde oplevert.

Tijdens runtime als het aantal items dat moet worden toegewezen een negatieve waarde is, is het gedrag niet gedefinieerd. Als deze nul is, wordt er geen toewijzing gemaakt en wordt de geretourneerde waarde door de implementatie gedefinieerd. Als er onvoldoende geheugen beschikbaar is om de items toe te wijzen, wordt er een System.StackOverflowException gegenereerd.

Wanneer er een stackalloc_initializer aanwezig is:

  • Als unmanaged_type wordt weggelaten, wordt dit afgeleid volgens de regels voor het beste gemeenschappelijke type (§12.6.3.15) voor de reeks stackalloc_element_initializer-s.
  • Als constant_expression wordt weggelaten, wordt aangenomen dat het overeenkomt met het aantal stackalloc_element_initializers.
  • Indien de constant_expression aanwezig is, moet deze gelijk zijn aan het aantal stackalloc_element_initializer's.

Elke stackalloc_element_initializer heeft een impliciete conversie naar unmanaged_type (§10,2). De stackalloc_element_initializerelementen in het toegewezen geheugen initialiseren in toenemende volgorde, beginnend met het element bij index nul. Als er geen stackalloc_initializeris, is de inhoud van het zojuist toegewezen geheugen niet gedefinieerd.

Als een stackalloc_expression rechtstreeks optreedt als de initialisatie-expressie van een local_variable_declaration (§13.6.2), waarbij de local_variable_type een type aanwijzer (§23.3) of afgeleid (var), dan is het resultaat van de stackalloc_expression een aanwijzer van het type T* (§23,9). In dit geval moet de stackalloc_expression in onveilige code worden weergegeven. Anders is het resultaat van een stackalloc_expression een exemplaar van het type Span<T>, waarbij T het unmanaged_typeis:

  • Span<T> (§C.3) is een verwijzingstype (§16.2.3), dat een geheugenblok weergeeft, hier het blok dat door de stackalloc_expressionis toegewezen, als een indexeerbare verzameling getypte (T) items.
  • De eigenschap Length van het resultaat retourneert het aantal toegewezen items.
  • De indexeerder van het resultaat (§15,9) retourneert een variabele_referentie (§9,5) naar een item van het toegewezen blok en controleert het bereik.

Initialisaties voor stacktoewijzing zijn niet toegestaan in catch of finally blokken (§13.11).

Opmerking: er is geen manier om expliciet geheugen vrij te maken met behulp van stackalloc. eindnotitie

Alle stack-toegewezen geheugenblokken die zijn gemaakt tijdens de uitvoering van een functielid, worden automatisch verwijderd wanneer dat functielid terugkeert.

Behalve de stackalloc-operator biedt C# geen vooraf gedefinieerde constructies voor het beheren van niet-garbage verzameld geheugen. Dergelijke services worden doorgaans geleverd door ondersteunende klassebibliotheken of rechtstreeks vanuit het onderliggende besturingssysteem geïmporteerd.

voorbeeld van:

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

In het geval van span8resulteert stackalloc in een Span<int>, die wordt geconverteerd door een impliciete operator naar ReadOnlySpan<int>. Op dezelfde manier wordt voor span9het resulterende Span<double> geconverteerd naar het door de gebruiker gedefinieerde type Widget<double> met behulp van de conversie, zoals wordt weergegeven. einde voorbeeld

12.8.23 De naam van de operator

Een nameof_expression wordt gebruikt om de naam van een programma-entiteit te verkrijgen als een constante tekenreeks.

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
    ;

Omdat nameof geen trefwoord is, is een nameof_expression altijd syntactisch dubbelzinnig met een aanroep van de eenvoudige naam nameof. Omwille van compatibiliteitsredenen, als een naamzoekactie (§12.8.4) van de naam nameof slaagt, wordt de uitdrukking beschouwd als een aanroep-uitdrukking, ongeacht of de aanroep geldig is. Anders is het een nameof_expression.

Eenvoudige naam- en lidtoegangsopzoekingen worden uitgevoerd op de named_entity tijdens de compilatietijd, volgens de regels beschreven in §12.8.4 en §12.8.7. Wanneer de zoekactie die wordt beschreven in §12.8.4 en §12.8.7 echter een fout oplevert omdat een exemplaarlid in een statische context is gevonden, veroorzaakt een nameof_expression geen dergelijke fout.

Het is een compilatiefout voor een named_entity die een methodegroep aanduidt om een type_argument_listte hebben. Het is een compilatietijdfout als een named_entity_target het type dynamicheeft.

Een nameof_expression is een constante expressie van het type stringen heeft geen effect tijdens runtime. Het named_entity ervan wordt niet geëvalueerd en wordt genegeerd voor een definitieve toewijzingsanalyse (§9.4.4.22). De waarde is de laatste identificator van de named_entity vóór de optionele laatste type_argument_list, veranderd op de volgende manier:

  • Het voorvoegsel "@", indien gebruikt, wordt verwijderd.
  • Elke unicode_escape_sequence wordt omgezet in het bijbehorende Unicode-teken.
  • Alle formatting_characters worden verwijderd.

Dit zijn dezelfde transformaties die worden toegepast in §6.4.3 bij het testen van gelijkheid tussen id's.

Voorbeeld: De volgende illustratie toont de resultaten van verschillende nameof expressies, ervan uitgaande dat een algemeen type List<T> binnen de System.Collections.Generic naamruimte is gedeclareerd.

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 { }
}

Mogelijk verrassende delen van dit voorbeeld zijn de resolutie van nameof(System.Collections.Generic) naar alleen 'Algemeen' in plaats van de volledige naamruimte, en van nameof(TestAlias) naar 'TestAlias' in plaats van 'Tekenreeks'. einde voorbeeld

12.8.24 Anonieme methodeexpressies

Een anonymous_method_expression is een van de twee manieren om een anonieme functie te definiëren. Deze worden verder beschreven in §12.19.

12.9 Unaire operatoren

12.9.1 Algemeen

De +, -, ! (logische negatie §12.9.4 alleen), ~, ++, --, cast- en await operatoren worden de unaire operatoren genoemd.

Opmerking: de postfix null-forgiving operator (§12.8.9), !, vanwege zijn aard dat het alleen tijdens compilatietijd is en niet-overlaadbaar, wordt uitgesloten van de bovenstaande lijst. eindnotitie

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) en addressof_expression (§23.6.5) zijn alleen beschikbaar in onveilige code (§23).

Als de operand van een unary_expression het type compileertijd heeft dynamic, is deze dynamisch gebonden (§12.3.3). In dit geval is het compilatietijdtype van de unary_expressiondynamic, en de onderstaande resolutie zal plaatsvinden tijdens de uitvoeringstijd met behulp van het uitvoeringstijdtype van de operand.

12.9.2 Unary plus operator

Voor een werking van het formulier +xwordt een unaire operator overbelastingsresolutie (§12.4.4) toegepast om een specifieke operator-implementatie te selecteren. De operand wordt geconverteerd naar het parametertype van de geselecteerde operator en het type van het resultaat is het retourtype van de operator. De vooraf gedefinieerde unaire plusoperators zijn:

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

Voor elk van deze operators is het resultaat gewoon de waarde van de operand.

Opgeheven (§12.4.8) vormen van de niet-opgeheven vooraf gedefinieerde unaire plus hierboven gedefinieerde operatoren zijn ook vooraf gedefinieerd.

12.9.3 Unaire mintekenoperator

Voor een werking van het formulier –xwordt een unaire operator overbelastingsresolutie (§12.4.4) toegepast om een specifieke operator-implementatie te selecteren. De operand wordt geconverteerd naar het parametertype van de geselecteerde operator en het type van het resultaat is het retourtype van de operator. De vooraf gedefinieerde unaire mintekenoperators zijn:

  • Negatie van gehele getallen:

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

    Het resultaat wordt berekend door X van nul af te trekken. Als de waarde van X de kleinste vertegenwoordigbare waarde is van het operandtype (−2¹ voor int of −2⁶³ voor long), is de wiskundige negatie van X niet zichtbaar binnen het operandtype. Als dit gebeurt binnen een checked context, wordt er een System.OverflowException gegenereerd; als deze zich in een unchecked context voordoet, is het resultaat de waarde van de operand en wordt de overloop niet gerapporteerd.

    Als de operand van de negatieoperator van het type uintis, wordt deze geconverteerd naar het type longen wordt het type van het resultaat long. Een uitzondering is de regel waarmee de int waarde −2147483648 (−2¹¹) kan worden geschreven als een letterlijk geheel getal voor decimalen (§6.4.5.3).

    Als de operand van de negatieoperator van het type ulongis, treedt er een compilatietijdfout op. Een uitzondering is de regel waarmee de long waarde −9223372036854775808 (−2⁶³) kan worden geschreven als een letterlijk decimaal geheel getal (§6.4.5.3)

  • Negatie met drijvende komma:

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

    Het resultaat is de waarde van X waarbij het teken is omgekeerd. Als xNaNis, wordt het resultaat ook NaN.

  • Decimale negatie

    decimal operator –(decimal x);
    

    Het resultaat wordt berekend door X van nul af te trekken. Decimaal negatie is gelijk aan het gebruik van de unaire min-operator van het type System.Decimal.

Opgeheven (§12.4.8) vormen van de onopgeheven, voorgedefinieerde unaire min-operatoren die hierboven zijn gedefinieerd, zijn ook voorgedefinieerd.

12.9.4 Logische negatieoperator

Voor een werking van het formulier !xwordt een unaire operator overbelastingsresolutie (§12.4.4) toegepast om een specifieke operator-implementatie te selecteren. De operand wordt geconverteerd naar het parametertype van de geselecteerde operator en het type van het resultaat is het retourtype van de operator. Er bestaat slechts één vooraf gedefinieerde logische negatieoperator:

bool operator !(bool x);

Deze operator berekent de logische negatie van de operand: Als de operand is true, wordt het resultaat false. Als de operand falseis, wordt het resultaat true.

Geheven (§12.4.8) vormen van de niet-geheven logische negatieoperator die hierboven is gedefinieerd, zijn eveneens vooraf gedefinieerd.

Opmerking: De voorvoegseloperator voor logische negatie en de postfix null-forgiving operator (§12.8.9) worden weliswaar door hetzelfde lexicale token (!) uitgebeeld, maar zijn verschillend. eindnotitie

12.9.5 Bitsgewijze complementoperator

Voor een werking van het formulier ~xwordt een unaire operator overbelastingsresolutie (§12.4.4) toegepast om een specifieke operator-implementatie te selecteren. De operand wordt geconverteerd naar het parametertype van de geselecteerde operator en het type van het resultaat is het retourtype van de operator. De vooraf gedefinieerde bitsgewijze complementoperators zijn:

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

Voor elk van deze operators is het resultaat van de bewerking het bitsgewijze complement van x.

Elk opsommingstype E impliciet de volgende bitsgewijze complementoperator biedt:

E operator ~(E x);

Het resultaat van het evalueren van ~x, waarbij X een expressie is van een opsommingstype E met een onderliggend type U, is precies hetzelfde als het evalueren van (E)(~(U)x), behalve dat de conversie naar E altijd wordt uitgevoerd alsof in een unchecked context (§12.8.20).

Opgeheven (§12.4.8) vormen van de niet-opgeheven vooraf gedefinieerde bitwise complementoperators die hierboven zijn gedefinieerd, zijn ook vooraf gedefinieerd.

12.9.6 Voorvoegsel incrementeer en decrementeer operatoren

pre_increment_expression
    : '++' unary_expression
    ;

pre_decrement_expression
    : '--' unary_expression
    ;

De operand van een voorvoegselverhoging of -degradatiebewerking moet een expressie zijn die is geclassificeerd als een variabele, een eigenschapstoegang of een indexeerfunctietoegang. Het resultaat van de bewerking is een waarde van hetzelfde type als de operand.

Als de operand van het voorvoegsel van een incrementele of decrementele bewerking een eigenschap of indexertoegang is, moet de eigenschap of indexer zowel een get- als een set-toegangsfunctie hebben. Als dit niet het geval is, treedt er een bindingstijdfout op.

De oplossing voor overbelasting van unaire operatoren (§12.4.4) wordt toegepast om een specifieke operator-implementatie te selecteren. Vooraf gedefinieerde ++- en -- operators bestaan voor de volgende typen: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimalen een opsommingstype. De vooraf gedefinieerde ++ operators retourneren de waarde die wordt geproduceerd door 1 toe te voegen aan de operand en de vooraf gedefinieerde -- operators retourneren de waarde die wordt geproduceerd door 1 af te trekken van de operand. Als in een checked-context het resultaat van deze optelling of aftrekking buiten het bereik van het resultaattype valt en het resultaattype een integraal type of opsommingstype is, wordt er een System.OverflowException gegooid.

Er moet een impliciete conversie zijn van het retourtype van de geselecteerde unaire operator naar het type van de unary_expression, anders treedt er een compilatietijdfout op.

De runtimeverwerking van een voorvoegselverhoging of -degradatiebewerking van het formulier ++x of --x bestaat uit de volgende stappen:

  • Als x is geclassificeerd als een variabele:
    • x wordt geëvalueerd om de variabele te produceren.
    • De waarde van x wordt geconverteerd naar het operandtype van de geselecteerde operator en de operator wordt aangeroepen met deze waarde als argument.
    • De waarde die door de operator wordt geretourneerd, wordt geconverteerd naar het type x. De resulterende waarde wordt opgeslagen op de locatie die is opgegeven door de evaluatie van x en wordt het resultaat van de bewerking.
  • Als x is geclassificeerd als een eigenschap of indexertoegang:
    • De exemplaarexpressie (als x niet staticis) en de lijst met argumenten (als x een indexeerfunctietoegang is) die aan x is gekoppeld, worden geëvalueerd en de resultaten worden gebruikt in de daaropvolgende aanroepen van de get- en set-toegangsmethodes.
    • De get-accessor van x wordt aangeroepen.
    • De waarde die door de get-accessor wordt geretourneerd, wordt geconverteerd naar het operandtype van de geselecteerde operator en de operator wordt aangeroepen met deze waarde als argument.
    • De waarde die door de operator wordt geretourneerd, wordt geconverteerd naar het type x. De set accessor van x wordt aangeroepen met deze waarde als waardeargument.
    • Deze waarde wordt ook het resultaat van de bewerking.

De operators ++ en -- ondersteunen ook postfix-notatie (§12.8.16). Het resultaat van x++ of x-- is de waarde van x vóór de bewerking, terwijl het resultaat van ++x of --x de waarde is van x na de bewerking. In beide gevallen heeft x zelf dezelfde waarde na de bewerking.

Een operator ++- of operator ---implementatie kan worden aangeroepen met behulp van een achtervoegsel- of voorvoegselnotatie. Het is niet mogelijk om afzonderlijke operator-implementaties te hebben voor de twee notaties.

Opgeheven (§12.4.8) vormen van de niet-opgeheven vooraf gedefinieerde voorvoegsel- en decrementoperatoren die hierboven zijn gedefinieerd, zijn ook vooraf gedefinieerd.

12.9.7 Cast-expressies

Een cast_expression wordt gebruikt om expliciet een expressie te converteren naar een bepaald type.

cast_expression
    : '(' type ')' unary_expression
    ;

Een cast_expression van de vorm (T)E, waarin T een type is en E een unary_expressionis, voert een expliciete conversie uit (§10.3) van de waarde van E naar type T. Als er geen expliciete conversie bestaat van E naar T, treedt er een bindingstijdfout op. Anders is het resultaat de waarde die wordt geproduceerd door de expliciete conversie. Het resultaat wordt altijd geclassificeerd als een waarde, zelfs als E een variabele aangeeft.

De grammatica voor een cast_expression leidt tot bepaalde syntactische ambiguïteiten.

voorbeeld: de expressie (x)–y kan worden geïnterpreteerd als een cast_expression (een cast van –y om xte typen) of als een additive_expression gecombineerd met een parenthesized_expression (waarmee de waarde x – ywordt berekend). einde voorbeeld

Om cast_expression dubbelzinnigheden op te lossen, bestaat de volgende regel: Een reeks tokens (§6.4) tussen haakjes wordt beschouwd als het begin van een cast_expression alleen als ten minste één van de volgende waar is:

  • De volgorde van tokens is de juiste grammatica voor een type, maar niet voor een expressie.
  • De volgorde van tokens is de juiste grammatica voor een type en het token direct na de sluitende haakjes is het token "~", het token "!", het token "(", een identifier (§6.4.3), een letterlijk (§6.4.5), of een trefwoord (§6.4.4) met uitzondering van as en is.

De term "juiste grammatica" hierboven betekent alleen dat de reeks tokens voldoet aan de specifieke grammaticale productie. Het houdt in het bijzonder geen rekening met de werkelijke betekenis van eventuele samenstellende id's.

voorbeeld: als x en y id's zijn, is x.y de juiste grammatica voor een type, zelfs als x.y geen type aangeeft. einde voorbeeld

Opmerking: Uit de ondubbelzinnigheidsregel volgt dat, als x en y id's zijn, (x)y, (x)(y)en (x)(-y)cast_expressionzijn, maar (x)-y niet, zelfs niet als x een type identificeert. Als x echter een trefwoord is waarmee een vooraf gedefinieerd type (zoals int) wordt geïdentificeerd, worden alle vier de formulieren cast_expressions (omdat een dergelijk trefwoord geen expressie kan zijn). eindnotitie

12.9.8 Awaits-expressies

12.9.8.1 Algemeen

De operator await wordt gebruikt om de evaluatie van de omringende asynchrone functie op te schorten totdat de asynchrone bewerking die wordt vertegenwoordigd door de operand is voltooid.

await_expression
    : 'await' unary_expression
    ;

Een await_expression is alleen toegestaan in de hoofdtekst van een asynchrone functie (§15.15). Binnen de dichtstbijzijnde insluitende asynchrone functie vindt een await_expression niet plaats op deze plaatsen:

  • Binnen een geneste (niet-asynchrone) anonieme functie
  • Binnen het blok van een lock_statement
  • In een anonieme functieconversie naar een expressiestructuurtype (§10.7.3)
  • In een onveilige context

Opmerking: een await_expression kan niet voorkomen op de meeste plaatsen binnen een query_expression, omdat deze syntactisch zijn getransformeerd om niet-asynchrone lambda-expressies te gebruiken. eindnotitie

Binnen een asynchrone functie wordt await niet gebruikt als een available_identifier, hoewel de exacte aanduiding @await kan worden gebruikt. Er is dus geen syntactische dubbelzinnigheid tussen await_expressions en verschillende expressies met id's. Buiten asynchrone functies fungeert await als een normale id.

De operand van een await_expression wordt de taakgenoemd. Het vertegenwoordigt een asynchrone bewerking die al dan niet voltooid is op het moment dat de await_expression wordt geëvalueerd. Het doel van de operator await is het onderbreken van de uitvoering van de omhullende asynchrone functie totdat de afgewachte taak is voltooid en vervolgens het resultaat ervan verkrijgen.

12.9.8.2 Wachtbare uitdrukkingen

De taak van een await_expression moet afwachting mogelijk maken. Een expressie t kan worden verwacht als een van de volgende bewaringen geldt:

  • t is van het compilatietijdstype dynamic
  • t heeft een toegankelijke instantie of extensiemethode met de naam GetAwaiter zonder parameters en zonder typeparameters, en een retourtype A waarvoor al het volgende van toepassing is:
    • A implementeert de interface System.Runtime.CompilerServices.INotifyCompletion (hierna bekend als INotifyCompletion voor beknoptheid)
    • A heeft een toegankelijke, leesbare instantieeigenschap IsCompleted van het type bool
    • A heeft een toegankelijke exemplaarmethode GetResult zonder parameters en geen typeparameters

Het doel van de GetAwaiter-methode is om een afwachter voor een taak te verkrijgen. Het type A wordt het afwachtertype genoemd voor de await-expressie.

Het doel van de eigenschap IsCompleted is om te bepalen of de taak al is voltooid. Zo ja, dan hoeft u de evaluatie niet op te schorten.

Het doel van de INotifyCompletion.OnCompleted methode is het registreren van een "vervolg" voor de taak; Een gemachtigde (van het type System.Action) die wordt aangeroepen zodra de taak is voltooid.

Het doel van de GetResult methode is het verkrijgen van het resultaat van de taak zodra deze is voltooid. Dit resultaat kan een succesvolle voltooiing zijn, mogelijk met een resultaatwaarde, of het kan een uitzondering zijn die wordt gegooid door de methode GetResult.

12.9.8.3 Classificatie van await-expressies

De expressie await t wordt op dezelfde manier geclassificeerd als de expressie (t).GetAwaiter().GetResult(). Als het retourtype van GetResult dus voidis, wordt de await_expression geclassificeerd als niets. Als het een niet-void retourtype Theeft, wordt de await_expression geclassificeerd als een waarde van het type T.

12.9.8.4 Runtime-evaluatie van await-expressies

Tijdens runtime wordt de expressie await t als volgt geëvalueerd:

  • Een awaiter a wordt verkregen door de expressie (t).GetAwaiter()te evalueren.
  • Een boolb wordt verkregen door de uitdrukking (a).IsCompletedte evalueren.
  • Als b is false, hangt de evaluatie ervan af of a de interface-System.Runtime.CompilerServices.ICriticalNotifyCompletion implementeert (hierna bekend als ICriticalNotifyCompletion voor beknoptheid). Deze controle wordt uitgevoerd op het moment van binden; d.w.z., tijdens runtime als a het compileertijd type dynamicheeft, en anders tijdens compileertijd. Geef r de hervattingsdelegatie aan (§15.15):
    • Als a geen ICriticalNotifyCompletionimplementeert, wordt de expressie ((a) as INotifyCompletion).OnCompleted(r) geëvalueerd.
    • Als aICriticalNotifyCompletionimplementeert, wordt de expressie ((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r) geëvalueerd.
    • De evaluatie wordt vervolgens onderbroken en het besturingselement wordt teruggezet naar de huidige aanroeper van de asynchrone functie.
  • Hetzij direct na (indien b is true) of bij latere aanroep van de hervattingsdelegatie (indien b is false), wordt de expressie (a).GetResult() geëvalueerd. Als er een waarde wordt geretourneerd, is die waarde het resultaat van de await_expression. Anders is het resultaat niets.

De implementatie van de interfacemethoden INotifyCompletion.OnCompleted en ICriticalNotifyCompletion.UnsafeOnCompleted moet ervoor zorgen dat de gedelegeerde r maximaal één keer wordt aangeroepen. Anders is het gedrag van de ingesloten asynchrone functie niet gedefinieerd.

12.10 Rekenkundige operatoren

12.10.1 Algemeen

De operatoren *, /, %, +en - worden de rekenkundige operatoren genoemd.

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
    ;

Als een operand van een rekenkundige operator het type compileertijd heeft dynamic, is de expressie dynamisch gebonden (§12.3.3). In dit geval is het type compilatietijd van de expressie dynamicen vindt de onderstaande resolutie plaats tijdens runtime met behulp van het runtimetype van die operanden met het type compileertijd dynamic.

12.10.2 Vermenigvuldigingsoperator

Voor een werking van het formulier x * ywordt overbelastingsresolutie van binaire operatoren (§12.4.5) toegepast om een specifieke operator-implementatie te selecteren. De operanden worden geconverteerd naar de parametertypen van de geselecteerde operator en het type van het resultaat is het retourtype van de operator.

De vooraf gedefinieerde vermenigvuldigingsoperatoren worden hieronder vermeld. De operators berekenen allemaal het product van x en y.

  • Vermenigvuldiging van gehele getallen:

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

    In een checked context, als het product buiten het bereik van het resultaattype valt, wordt er een System.OverflowException opgeworpen. In een unchecked context worden overloop niet gerapporteerd en worden belangrijke bits met hoge volgorde buiten het bereik van het resultaattype verwijderd.

  • Vermenigvuldiging van drijvende komma:

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

    Het product wordt berekend volgens de regels van IEC 60559 rekenkundige berekeningen. De volgende tabel bevat de resultaten van alle mogelijke combinaties van niet-nulwaarden, nullen, infiniteiten en NaN's. In de tabel zijn x en y positieve eindige waarden. z is het resultaat van x * y, afgerond op de dichtstbijzijnde vertegenwoordigbare waarde. Als de grootte van het resultaat te groot is voor het doeltype, z oneindig is. Vanwege afronding kan z nul zijn, ook al is x noch y nul.

    +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

    (Tenzij anders vermeld, betekent in de tabellen met drijvende komma in §12.10.2§12.10.6 het gebruik van "+" de waarde positief is; het gebruik van "-" betekent dat de waarde negatief is; en het ontbreken van een teken betekent dat de waarde positief of negatief kan zijn of geen teken heeft (NaN).)

  • Decimale vermenigvuldiging:

    decimal operator *(decimal x, decimal y);
    

    Als de grootte van de resulterende waarde te groot is om weer te geven in de decimale notatie, wordt er een System.OverflowException gegenereerd. Vanwege afronding kan het resultaat nul zijn, ook al is geen operand nul. De schaal van het resultaat, vóór afronding, is de som van de schalen van de twee operanden. Decimale vermenigvuldiging is gelijk aan het gebruik van de vermenigvuldigingsoperator van het type System.Decimal.

Opgeheven (§12.4.8) vormen van de niet-opgeheven vooraf gedefinieerde vermenigvuldigingsoperatoren die hierboven zijn gedefinieerd, zijn ook vooraf gedefinieerd.

12.10.3 Divisieoperator

Voor een werking van het formulier x / ywordt overbelastingsresolutie van binaire operatoren (§12.4.5) toegepast om een specifieke operator-implementatie te selecteren. De operanden worden geconverteerd naar de parametertypen van de geselecteerde operator en het type van het resultaat is het retourtype van de operator.

Hieronder vindt u de vooraf gedefinieerde divisieoperators. De operators berekenen allemaal het quotiënt van x en y.

  • Deling van gehele getallen:

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

    Als de waarde van de rechteroperand nul is, wordt er een System.DivideByZeroException gegooid.

    De divisie rondt het resultaat af op nul. De absolute waarde van het resultaat is dus het grootst mogelijke gehele getal dat kleiner is dan of gelijk is aan de absolute waarde van het quotiënt van de twee operanden. Het resultaat is nul of positief wanneer de twee operanden hetzelfde teken en nul of negatief hebben wanneer de twee operanden tegengestelde tekens hebben.

    Als de linkeroperand de kleinste vertegenwoordigbare int of long waarde is en de rechteroperand –1is, treedt er een overloop op. In een checked context wordt hierdoor een System.ArithmeticException (of een subklasse daarvan) opgeworpen. In een unchecked-context ligt het aan de implementatie of een System.ArithmeticException (of een subklasse daarvan) wordt geworpen of dat de overloop niet gerapporteerd wordt, met als resulterende waarde die van de linkeroperand.

  • Deling met drijvende komma

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

    Het quotiënt wordt berekend volgens de regels van IEC 60559-rekenkundige bewerkingen. De volgende tabel bevat de resultaten van alle mogelijke combinaties van niet-nulwaarden, nullen, infiniteiten en NaN's. In de tabel zijn x en y positieve eindige waarden. z is het resultaat van x / y, afgerond op de dichtstbijzijnde vertegenwoordigbare waarde.

    +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
  • Decimaal deling:

    decimal operator /(decimal x, decimal y);
    

    Als de waarde van de rechteroperand nul is, wordt er een System.DivideByZeroException gegooid. Als de grootte van de resulterende waarde te groot is om weer te geven in de decimale notatie, wordt er een System.OverflowException gegenereerd. Vanwege afronding kan het resultaat nul zijn, ook al is de eerste operand niet nul. De schaal van het resultaat, vóór afronding, is de dichtstbijzijnde schaal naar de voorkeursschaal die een resultaat behoudt dat gelijk is aan het exacte resultaat. De voorkeursschaal is de schaal van x minder de schaal van y.

    Decimaal deling is gelijk aan het gebruik van de delingsoperator van het type System.Decimal.

Opgeheven (§12.4.8) vormen van de niet-opgeheven vooraf gedefinieerde divisieoperators die hierboven zijn gedefinieerd, zijn ook vooraf gedefinieerd.

12.10.4 Restoperator

Voor een werking van het formulier x % ywordt overbelastingsresolutie van binaire operatoren (§12.4.5) toegepast om een specifieke operator-implementatie te selecteren. De operanden worden geconverteerd naar de parametertypen van de geselecteerde operator en het type van het resultaat is het retourtype van de operator.

De vooraf gedefinieerde restoperators worden hieronder weergegeven. De operators berekenen allemaal de rest van de deling tussen x en y.

  • Rest van een geheel getal

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

    Het resultaat van x % y is de waarde die wordt geproduceerd door x – (x / y) * y. Als y nul is, wordt er een System.DivideByZeroException opgeworpen.

    Als de linkeroperand de kleinste int of long waarde is en de rechteroperand –1is, wordt er een System.OverflowException gegenereerd als en alleen als x / y een uitzondering zou genereren.

  • Rest van drijvende komma:

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

    De volgende tabel bevat de resultaten van alle mogelijke combinaties van niet-nulwaarden, nullen, infiniteiten en NaN's. In de tabel zijn x en y positieve eindige waarden. z is het resultaat van x % y en wordt berekend als x – n * y, waarbij n het grootst mogelijke gehele getal is dat kleiner is dan of gelijk is aan x / y. Deze methode voor het berekenen van de rest is vergelijkbaar met de methode die wordt gebruikt voor gehele getallen, maar verschilt van de IEC 60559-definitie (waarbij n het gehele getal is dat het dichtst bij x / yligt).

    +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
  • Decimale restwaarde

    decimal operator %(decimal x, decimal y);
    

    Als de waarde van de rechteroperand nul is, wordt er een System.DivideByZeroException gegooid. Het is implementatie-afhankelijk wanneer een System.ArithmeticException (of een subklasse daarvan) wordt opgeworpen. Een conforme implementatie genereert geen uitzondering voor x % y in elk geval wanneer x / y geen uitzondering genereert. De schaal van het resultaat, vóór afronding, is de grotere schaal van de twee operanden en het teken van het resultaat is, indien niet-nul, gelijk aan dat van x.

    Decimale rest is gelijk aan het gebruik van de restoperator van type System.Decimal.

    Opmerking: deze regels zorgen ervoor dat voor alle typen het resultaat nooit het tegenovergestelde teken van de linkeroperand heeft. eindnotitie

Opgeheven (§12.4.8) vormen van de niet-opgeheven vooraf gedefinieerde restoperators die hierboven zijn gedefinieerd, zijn ook vooraf gedefinieerd.

12.10.5 Plusoperator

Voor een werking van het formulier x + ywordt overbelastingsresolutie van binaire operatoren (§12.4.5) toegepast om een specifieke operator-implementatie te selecteren. De operanden worden geconverteerd naar de parametertypen van de geselecteerde operator en het type van het resultaat is het retourtype van de operator.

De vooraf gedefinieerde toevoegingsoperatoren worden hieronder vermeld. Voor numerieke en opsommingstypen berekenen de vooraf gedefinieerde opteloperators de som van de twee operanden. Wanneer een of beide operanden van het type stringzijn, voegen de vooraf gedefinieerde opteloperators de tekenreeksweergave van de operanden samen.

  • Optellen van gehele getallen:

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

    In een checked-context, als de som buiten het bereik van het resultaattype valt, wordt er een System.OverflowException geworpen. In een unchecked context worden overloop niet gerapporteerd en worden belangrijke bits met hoge volgorde buiten het bereik van het resultaattype verwijderd.

  • Toevoeging van drijvende komma:

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

    De som wordt berekend volgens de regels van IEC 60559-rekenkundige berekeningen. De volgende tabel bevat de resultaten van alle mogelijke combinaties van niet-nulwaarden, nullen, infiniteiten en NaN's. In de tabel zijn x en y niet-nul eindige waarden en is z het resultaat van x + y. Als x en y dezelfde grootte maar tegengestelde tekens hebben, is z positief nul. Als x + y te groot is om aan te geven in het doeltype, is z een oneindigheid met hetzelfde teken als x + y.

    y +0 -0 +∞ -∞ NaN
    x z x x +∞ -∞ NaN
    +0 y +0 +0 +∞ –∞ NaN
    -0 y +0 -0 +∞ -∞ NaN
    +∞ +∞ +∞ +∞ +∞ NaN NaN
    -∞ -∞ -∞ -∞ NaN -∞ NaN
    NaN NaN NaN NaN NaN NaN NaN
  • Decimaal optellen:

    decimal operator +(decimal x, decimal y);
    

    Als de grootte van de resulterende waarde te groot is om weer te geven in de decimale notatie, wordt er een System.OverflowException gegenereerd. De schaal van het resultaat, voordat het wordt afgerond, is de grotere schaal van de twee operanden.

    Decimaal optellen komt overeen met het gebruik van de optellingsoperator van het type System.Decimal.

  • Opsommingstoevoeging. Elk opsommingstype biedt impliciet de volgende vooraf gedefinieerde operators, waarbij E het enumtype is en U het onderliggende type Eis:

    E operator +(E x, U y);
    E operator +(U x, E y);
    

    Tijdens runtime worden deze operators exact geëvalueerd als (E)((U)x + (U)y).

  • Tekenreekssamenvoeging:

    string operator +(string x, string y);
    string operator +(string x, object y);
    string operator +(object x, string y);
    

    Deze overbelastingen van de binaire +-operator voeren tekenreekssamenvoeging uit. Als een operand van tekenreekssamenvoeging nullis, wordt een lege tekenreeks vervangen. Anders wordt elke niet-string operand geconverteerd naar de tekenreeksweergave door de virtuele ToString methode aan te roepen die is overgenomen van het type object. Als ToStringnullretourneert, wordt een lege tekenreeks vervangen.

    voorbeeld van:

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

    De uitvoer die in de opmerkingen wordt weergegeven, is het typische resultaat van een US-English systeem. De exacte uitvoer kan afhankelijk zijn van de regionale instellingen van de uitvoeringsomgeving. De operator voor tekenreekssamenvoeging gedraagt zich in elk geval op dezelfde manier, maar de ToString methoden die impliciet worden aangeroepen tijdens de uitvoering, kunnen worden beïnvloed door regionale instellingen.

    einde voorbeeld

    Het resultaat van de tekenreekssamenvoegingsoperator is een string die bestaat uit de tekens van de linkeroperand, gevolgd door de tekens van de rechteroperand. De operator voor tekenreekssamenvoeging retourneert nooit een null waarde. Er kan een System.OutOfMemoryException worden gegenereerd als er onvoldoende geheugen beschikbaar is om de resulterende tekenreeks toe te wijzen.

  • Gedelegeerde combinatie. Elk type gemachtigde biedt impliciet de volgende vooraf gedefinieerde operator, waarbij D het type gedelegeerde is:

    D operator +(D x, D y);
    

    Als de eerste operand nullis, is het resultaat van de bewerking de waarde van de tweede operand (zelfs als dat ook null). Als de tweede operand nullis, is het resultaat van de bewerking de waarde van de eerste operand. Anders is het resultaat van de bewerking een nieuw gemachtigde exemplaar waarvan de aanroeplijst bestaat uit de elementen in de aanroeplijst van de eerste operand, gevolgd door de elementen in de aanroeplijst van de tweede operand. Dit betekent dat de aanroepenlijst van de resulterende delegate de concatenatie is van de aanroepenlijsten van de twee operanden.

    Opmerking: zie §12.10.6 en §20.6voor voorbeelden van combinatie van gemachtigden. Omdat System.Delegate geen gemachtigdentype is, is operator + niet gedefinieerd. eindnotitie

Opgeheven (§12.4.8) vormen van de niet-opgeheven vooraf gedefinieerde toevoegingsoperators zijn ook vooraf gedefinieerd.

12.10.6 Aftrekkingsoperator

Voor een werking van het formulier x – ywordt overbelastingsresolutie van binaire operatoren (§12.4.5) toegepast om een specifieke operator-implementatie te selecteren. De operanden worden geconverteerd naar de parametertypen van de geselecteerde operator en het type van het resultaat is het retourtype van de operator.

Hieronder vindt u de vooraf gedefinieerde aftrekkingsoperators. De operators trekken allemaal y van xaf.

  • Geheel getal aftrekken:

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

    In een checked context, als het verschil buiten het bereik van het resultaattype valt, wordt een System.OverflowException geworpen. In een unchecked context worden overloop niet gerapporteerd en worden belangrijke bits met hoge volgorde buiten het bereik van het resultaattype verwijderd.

  • Aftrekken van drijvende komma:

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

    Het verschil wordt berekend volgens de regels van IEC 60559 rekenkundige berekeningen. De volgende tabel bevat de resultaten van alle mogelijke combinaties van niet-nulwaarden, nullen, infiniteiten en NaN's. In de tabel zijn x en y niet-nul eindige waarden en is z het resultaat van x – y. Als x en y gelijk zijn, is z positief nul. Als x – y te groot is om aan te geven in het doeltype, is z een oneindigheid met hetzelfde teken als x – y.

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

    (In de bovenstaande tabel geven de -y vermeldingen de negatie van yaan, niet dat de waarde negatief is.)

  • Decimaal aftrekken:

    decimal operator –(decimal x, decimal y);
    

    Als de grootte van de resulterende waarde te groot is om weer te geven in de decimale notatie, wordt er een System.OverflowException gegenereerd. De schaal van het resultaat, voordat het wordt afgerond, is de grotere schaal van de twee operanden.

    Decimaal aftrekken is gelijk aan het gebruik van de aftrekkingsoperator van het type System.Decimal.

  • Aftrekken van een opsomming Elk opsommingstype biedt impliciet de volgende vooraf gedefinieerde operator, waarbij E het enumtype is en U het onderliggende type Eis:

    U operator –(E x, E y);
    

    Deze operator wordt op dezelfde wijze geëvalueerd als (U)((U)x – (U)y). Met andere woorden, de operator berekent het verschil tussen de rangtelwaarden van x en y, en het type van het resultaat is het onderliggende type van de opsomming.

    E operator –(E x, U y);
    

    Deze operator wordt op dezelfde wijze geëvalueerd als (E)((U)x – y). Met andere woorden, de operator trekt een waarde af van het onderliggende type van de opsomming, wat resulteert in een waarde van de opsomming.

  • Verwijdering van de afgevaardigde. Elk type gemachtigde biedt impliciet de volgende vooraf gedefinieerde operator, waarbij D het type gedelegeerde is:

    D operator –(D x, D y);
    

    De semantiek is als volgt:

    • Als de eerste operand nullis, wordt het resultaat van de bewerking null.
    • Als de tweede operand nullis, is het resultaat van de bewerking de waarde van de eerste operand.
    • Anders vertegenwoordigen beide operanden niet-lege aanroeplijsten (§20.2).
      • Als de lijsten gelijk zijn, zoals bepaald door de gemachtigde gelijkheidsoperator (§12.12.9), wordt het resultaat van de bewerking null.
      • Anders is het resultaat van de bewerking een nieuwe aanroeplijst die bestaat uit de lijst van de eerste operand met de vermeldingen van de tweede operand die uit de lijst zijn verwijderd, mits de lijst van de tweede operand een sublijst van de eerste operand is. (Om gelijkheid in sublijsten te bepalen, worden overeenkomende vermeldingen vergeleken met de operator voor gedelegeerde gelijkheid.) Als de lijst van de tweede operand overeenkomt met meerdere sublijsten met aaneengesloten vermeldingen in de lijst van de eerste operand, wordt de laatste overeenkomende sublijst met aaneengesloten items verwijderd.
      • Anders is het resultaat van de bewerking de waarde van de linkeroperand.

    Geen van de lijsten van operanden (indien aanwezig) wordt in het proces gewijzigd.

    voorbeeld van:

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

    einde voorbeeld

Opgeheven (§12.4.8) vormen van de vooraf gedefinieerde niet-verheven aftrekkingsoperators die hierboven zijn gedefinieerd, zijn eveneens vooraf gedefinieerd.

12.11 Shift-operatoren

De operators << en >> worden gebruikt om bitverschuifbewerkingen uit te voeren.

shift_expression
    : additive_expression
    | shift_expression '<<' additive_expression
    | shift_expression right_shift additive_expression
    ;

Als een operand van een shift_expression het type compileertijd heeft dynamic, is de expressie dynamisch gebonden (§12.3.3). In dit geval is het type compilatietijd van de expressie dynamicen vindt de onderstaande resolutie plaats tijdens runtime met behulp van het runtimetype van die operanden met het type compileertijd dynamic.

Voor een werking van het formulier x << count of x >> countwordt de overbelastingsresolutie van binaire operatoren (§12.4.5) toegepast om een specifieke operator-implementatie te selecteren. De operanden worden geconverteerd naar de parametertypen van de geselecteerde operator en het type van het resultaat is het retourtype van de operator.

Bij het declareren van een overbelaste ploegendienstoperator moet het type van de eerste operand altijd de klasse of struct zijn die de declaratie van de operator bevat en het type van de tweede operand altijd int.

De vooraf gedefinieerde shiftoperators worden hieronder weergegeven.

  • Naar links verschuiven:

    int operator <<(int x, int count);
    uint operator <<(uint x, int count);
    long operator <<(long x, int count);
    ulong operator <<(ulong x, int count);
    

    De operator << verschuift x naar links met een aantal bits, zoals beschreven in de onderstaande uitleg.

    De bits met hoge volgorde buiten het bereik van het resultaattype van x worden verwijderd, de resterende bits worden naar links verplaatst en de lege bitposities met lage volgorde worden ingesteld op nul.

  • Naar rechts gaan:

    int operator >>(int x, int count);
    uint operator >>(uint x, int count);
    long operator >>(long x, int count);
    ulong operator >>(ulong x, int count);
    

    De operator >> verschuift x naar rechts door een aantal bits dat is berekend, zoals hieronder wordt beschreven.

    Wanneer x van het type int of longis, worden de bits met lage volgorde van x verwijderd, worden de resterende bits naar rechts verplaatst en worden de lege bitposities in hoge volgorde ingesteld op nul als x niet-negatief is en ingesteld op een als x negatief is.

    Wanneer x van het type uint of ulongis, worden de bits met lage volgorde van x verwijderd, worden de resterende bits naar rechts verplaatst en worden de lege bitposities in hoge volgorde ingesteld op nul.

Voor de vooraf gedefinieerde operators wordt het aantal bits dat moet worden verplaatst als volgt berekend:

  • Wanneer het type xint of uintis, wordt het schuifgetal gegeven door de laagste vijf bits van count. Met andere woorden, het aantal diensten wordt berekend op basis van count & 0x1F.
  • Wanneer het type van xlong of ulongis, wordt het aantal verschuivingen gegeven door de lage order zes bits van count. Met andere woorden, het aantal diensten wordt berekend op basis van count & 0x3F.

Als het resulterende aantal diensten nul is, retourneren de shiftoperators gewoon de waarde van x.

Shift-bewerkingen veroorzaken nooit overflow en geven dezelfde resultaten in gecontroleerde en zonder controle contexten.

Wanneer de linkeroperand van de operator >> van een getekend integraal type is, voert de operator een rekenkundige verschuiving naar rechts uit, waarbij de waarde van de meest significante bit (de tekenbit) van de operand wordt doorgegeven aan de hoogste lege bitposities. Wanneer de linkeroperand van de operator >> van een niet-ondertekend integraal type is, voert de operator een logische verschuiving naar rechts uit, waarbij bitposities van de hoogste orde altijd op nul worden ingesteld. Om de omgekeerde bewerking uit te voeren die wordt afgeleid uit het operandtype, kunnen expliciete casts worden gebruikt.

voorbeeld: als x een variabele van het type intis, wordt met de bewerking unchecked ((int)((uint)x >> y)) een logische verschuiving van xuitgevoerd. einde voorbeeld

Opgeheven (§12.4.8) vormen van de niet-opgeheven, vooraf gedefinieerde shift-operatoren die hierboven zijn gedefinieerd, zijn ook vooraf gedefinieerd.

12.12 Relationele en typetestoperators

12.12.1 Algemeen

De operators ==, !=, <, >, <=, >=, isen as worden de operatoren voor relationele en typetests genoemd.

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
    ;

Opmerking: Het zoeken naar de juiste operand van de operator is moet eerst worden getest als een typeen vervolgens als een expressie die meerdere tokens kan omvatten. In het geval dat de operand een expressieis, moet de patroonexpressie ten minste evenveel prioriteit hebben als verschuivingsuitdrukking. eindnotitie

De is operator wordt beschreven in §12.12.12 en de operator as wordt beschreven in §12.12.13.

De operatoren ==, !=, <, >, <= en >= zijn vergelijkingsoperatoren.

Als een default_literal (§12.8.21) wordt gebruikt als operand van een <, >, <=of >= operator, treedt er een compilatietijdfout op. Als een default_literal wordt gebruikt als beide operanden van een ==- of !=-operator, treedt er een compilatietijdfout op. Als een default_literal wordt gebruikt als de linkeroperand van de operator is of as, treedt er een compilatietijdfout op.

Als een operand van een vergelijkingsoperator het type compileertijd heeft dynamic, is de expressie dynamisch gebonden (§12.3.3). In dit geval is het compile-tijdtype van de expressie dynamic, en zal de hieronder beschreven resolutie plaatsvinden tijdens de uitvoertijd met behulp van het uitvoertijdtype van die operanden die het compile-tijdtype dynamichebben.

Voor een werking van het formulier x «op» y, waarbij «op» een vergelijkingsoperator is, wordt overbelastingsresolutie (§12.4.5) toegepast om een specifieke operator-implementatie te selecteren. De operanden worden geconverteerd naar de parametertypen van de geselecteerde operator en het type van het resultaat is het retourtype van de operator. Als beide operanden van een equality_expression de letterlijke null zijn, wordt de overbelastingsresolutie niet uitgevoerd en wordt de expressie geëvalueerd tot een constante waarde van true of false, afhankelijk van of de operator == of !=is.

De vooraf gedefinieerde vergelijkingsoperatoren worden beschreven in de volgende subclauses. Alle vooraf gedefinieerde vergelijkingsoperatoren retourneren een resultaat van het type bool, zoals beschreven in de volgende tabel.

Operatie resultaat
x == y true als x gelijk is aan y, false anders
x != y true als x niet gelijk is aan y, false anders
x < y true als x kleiner is dan y, anders false
x > y true als x groter is dan y, false anders
x <= y true als x kleiner is dan of gelijk is aan y, false anders
x >= y true als x groter is dan of gelijk is aan y, false anders

12.12.2 Vergelijkingsoperatoren voor gehele getallen

De vooraf gedefinieerde vergelijkingsoperatoren voor gehele getallen zijn:

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

Elk van deze operatoren vergelijkt de numerieke waarden van de twee gehele getallen en retourneert een bool waarde die aangeeft of de specifieke relatie true of falseis.

Opgeheven (§12.4.8) vormen van de niet-opgeheven vooraf gedefinieerde vergelijkingsoperatoren voor gehele getallen die hierboven zijn gedefinieerd, zijn ook vooraf gedefinieerd.

12.12.3 Vergelijkingsoperators voor zwevende komma

De vooraf gedefinieerd kommagetalvergelijkingsoperatoren zijn:

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

De operators vergelijken de operanden volgens de regels van de IEC 60559-standaard:

Als een van beide operanden NaN is, wordt het resultaat false voor alle operators, met uitzondering van !=, waarvoor het resultaat trueis. Voor elke twee operanden produceert x != y altijd hetzelfde resultaat als !(x == y). Wanneer een of beide operanden Echter NaN zijn, produceren de <, >, <=en >= operators niet niet dezelfde resultaten als de logische negatie van de omgekeerde operator.

voorbeeld: als een van x en y NaN is, wordt x < yfalse, maar !(x >= y) is true. einde voorbeeld

Wanneer geen van beide operanden NaN is, vergelijken de operators de waarden van de twee drijvendekommaoperanden met betrekking tot de volgorde

–∞ < –max < ... < –min < –0.0 == +0.0 < +min < ... < +max < +∞

waarbij min en max de kleinste en grootste positieve eindige waarden zijn die in de opgegeven drijvende-kommaformaat kunnen worden weergegeven. Belangrijke effecten van deze volgorde zijn:

  • Negatieve en positieve nullen worden als gelijk beschouwd.
  • Een negatieve oneindigheid wordt beschouwd als kleiner dan alle andere waarden, maar gelijk aan een andere negatieve oneindigheid.
  • Een positief oneindigheid wordt beschouwd als groter dan alle andere waarden, maar gelijk aan een ander positief oneindigheid.

Gelifte (§12.4.8) vormen van de niet-gelifte vooraf gedefinieerde drijvende-punt vergelijkingsoperatoren die hierboven zijn gedefinieerd, zijn ook vooraf gedefinieerd.

12.12.4 Decimale vergelijkingsoperatoren

De vooraf gedefinieerde operatoren voor decimaalvergelijking zijn:

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

Elk van deze operatoren vergelijkt de numerieke waarden van de twee decimale operanden en retourneert een bool waarde die aangeeft of de specifieke relatie true of falseis. Elke decimale vergelijking is gelijk aan het gebruik van de overeenkomstige relationele of gelijkheidsoperator van het type System.Decimal.

Opgeheven (§12.4.8) vormen van de niet-opgeheven vooraf gedefinieerde decimale vergelijkingsoperatoren die hierboven zijn gedefinieerd, zijn ook vooraf gedefinieerd.

12.12.5 Booleaanse gelijkheidsoperators

De vooraf gedefinieerde Booleaanse gelijkheidsoperators zijn:

bool operator ==(bool x, bool y);
bool operator !=(bool x, bool y);

Het resultaat van == is true als zowel x als ytrue zijn of als zowel x als yfalsezijn. Anders wordt het resultaat false.

Het resultaat van != is false als zowel x als ytrue zijn of als zowel x als yfalsezijn. Anders wordt het resultaat true. Wanneer de operanden van het type boolzijn, produceert de operator != hetzelfde resultaat als de operator ^.

Opgeheven (§12.4.8) vormen van de niet-gelifte vooraf gedefinieerde booleaanse gelijkheidsoperators die hierboven zijn gedefinieerd, zijn eveneens vooraf gedefinieerd.

12.12.6 Operatoren voor vergelijking van opsommingen

Elk opsommingstype biedt impliciet de volgende vooraf gedefinieerde vergelijkingsoperatoren

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

Het resultaat van het evalueren van x «op» y, waarbij x en y expressies zijn van een opsommingstype E met een onderliggend type U, en «op» een van de vergelijkingsoperatoren is precies hetzelfde als het evalueren van ((U)x) «op» ((U)y). Met andere woorden, de vergelijkingsoperatoren voor opsommingstypen vergelijken de onderliggende integrale waarden van de twee operanden.

Opgeheven (§12.4.8) vormen van de niet-opgeheven vooraf gedefinieerde opsommingsoperators die hierboven zijn gedefinieerd, zijn ook vooraf gedefinieerd.

12.12.7 Verwijzingstype gelijkheidsoperators

Elk klassetype C impliciet de volgende vooraf gedefinieerde referentietype gelijkheidsoperators:

bool operator ==(C x, C y);
bool operator !=(C x, C y);

tenzij vooraf gedefinieerde gelijkheidsoperators anders bestaan voor C (bijvoorbeeld wanneer C is string of System.Delegate).

De operators retourneren het resultaat van het vergelijken van de twee verwijzingen op gelijkheid of ongelijkheid. operator == retourneert true als en alleen als x en y verwijzen naar hetzelfde exemplaar of beide nullzijn, terwijl operator !=true retourneert als en alleen als operator == met dezelfde operanden falsezou retourneren.

Naast de normale toepasbaarheidsregels (§12.6.4.2), vereisen de vooraf gedefinieerde referentietype gelijkheidsoperatoren een van de volgende voorwaarden om van toepassing te zijn:

  • Beide operanden zijn een waarde van een type dat bekend staat als een reference_type of de letterlijke null. Bovendien bestaat er een identiteits- of expliciete verwijzingsconversie (§10.3.5) van de ene operand naar het type van de andere operand.
  • De ene operand is de letterlijke nullen de andere operand is een waarde van een type T waarbij T een type_parameter is waarvan het niet bekend is of het een waardetype is, en geen waardetypebeperking heeft.
    • Als tijdens runtime T een niet-null-waardetype is, dan zijn de resultaten van == en false respectievelijk != en true.
    • Als tijdens runtime T een type null-waarde is, wordt het resultaat berekend op basis van de eigenschap HasValue van de operand, zoals beschreven in (§12.12.10).
    • Als tijdens runtime T een verwijzingstype is, wordt het resultaat true als de operand is nullen anders false.

Tenzij aan een van deze voorwaarden wordt voldaan, treedt er een bindingstijdfout op.

Opmerking: Belangrijke gevolgen van deze regels zijn:

  • Het is een bindingstijdfout om de vooraf gedefinieerde referentietype gelijkheidsoperators te gebruiken om twee verwijzingen te vergelijken die bekend zijn om verschillend te zijn tijdens bindingstijd. Als de bindingstijdtypen van de operanden bijvoorbeeld twee klassetypen zijn en geen van beide zijn afgeleid van de andere, is het onmogelijk voor de twee operanden om naar hetzelfde object te verwijzen. De bewerking wordt dus beschouwd als een bindingstijdfout.
  • De vooraf gedefinieerde operatoren voor verwijzingstype gelijkheid staan niet toe dat de operanden van het waardetype worden vergeleken (behalve wanneer typeparameters worden vergeleken met null, die speciaal worden verwerkt).
  • Operanden van vooraf gedefinieerde referentietype-gelijkheidsoperators worden nooit geboxed. Het zou zinloos zijn om dergelijke boxing-operaties uit te voeren, aangezien verwijzingen naar de nieuw toegewezen boxed instanties noodzakelijkerwijs afwijken van alle andere verwijzingen.

Voor een bewerking van de vorm x == y of x != y, indien een toepasselijke door de gebruiker gedefinieerde operator == of operator != bestaat, selecteren de regels voor het oplossen van operatoroverbelasting (§12.4.5) die operator in plaats van de vooraf gedefinieerde operator voor gelijkheid van referentietypen. Het is altijd mogelijk om de voorgeconfigureerde gelijkheidsoperator voor referentietypen te selecteren door expliciet een of beide operanden naar type objectte casten.

eindnotitie

Voorbeeld: In het volgende voorbeeld wordt gecontroleerd of een argument van een onbeperkt typeparameter nullis.

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

De x == null constructie is toegestaan, ook al kan T een niet-null-waardetype vertegenwoordigen en wordt het resultaat eenvoudig gedefinieerd om te worden false wanneer T een niet-null-waardetype is.

einde voorbeeld

Voor een bewerking van de vorm x == y of x != y, als er een toepasselijke operator == of operator != bestaat, selecteert de operator-overbelastingsresolutie (§12.4.5) die operator in plaats van de vooraf gedefinieerde operator voor gelijkheid van referentietypen.

Opmerking: Het is altijd mogelijk om de vooraf gedefinieerde operator voor gelijkheid van verwijzingstypen te selecteren door beide operanden expliciet naar type objectte casten. eindnotitie

voorbeeld: het voorbeeld

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

produceert de uitvoer

True
False
False
False

De variabelen s en t verwijzen naar twee afzonderlijke tekenreeksexemplaren met dezelfde tekens. De eerste vergelijking levert True op omdat de vooraf gedefinieerde operator voor tekenreeks gelijkheid (§12.12.8) is geselecteerd wanneer beide operanden van het type stringzijn. De resterende vergelijkingen geven allemaal False als uitvoer omdat de overbelasting van operator == in het string-type niet van toepassing is wanneer een van beide operanden een bindingstijdtype van objectheeft.

Houd er rekening mee dat de bovenstaande techniek niet zinvol is voor waardetypen. Het voorbeeld

class Test
{
    static void Main()
    {
        int i = 123;
        int j = 123;
        Console.WriteLine((object)i == (object)j);
    }
}

geeft False omdat de casts verwijzingen maken naar twee afzonderlijke exemplaren van geboxte int waarden.

einde voorbeeld

12.12.8 Operatoren voor gelijkheid van tekenreeksen

De vooraf gedefinieerde operatoren voor gelijkheid van tekenreeksen zijn:

bool operator ==(string x, string y);
bool operator !=(string x, string y);

Twee string waarden worden als gelijk beschouwd wanneer een van de volgende waarden waar is:

  • Beide waarden zijn null.
  • Beide waarden zijn niet-null verwijzingen naar tekenreeksexemplaren met identieke lengten en identieke tekens in elke tekenpositie.

De operatoren voor gelijkheid van tekenreeksen vergelijken tekenreekswaarden in plaats van tekenreeksverwijzingen. Wanneer twee afzonderlijke tekenreeksexemplaren exact dezelfde reeks tekens bevatten, zijn de waarden van de tekenreeksen gelijk, maar de verwijzingen verschillen.

Opmerking: zoals beschreven in §12.12.7, kunnen de operatoren voor gelijkheid van referentietypen worden gebruikt om tekenreeksverwijzingen te vergelijken in plaats van tekenreekswaarden. eindnotitie

12.12.9 Gelijkheidsoperators delegeren

De vooraf gedefinieerde operatoren voor gelijkheid van gemachtigden zijn:

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

Twee gedelegeerde instanties worden als gelijk beschouwd:

  • Als een van de gedelegeerde instanties nullis, zijn ze gelijk als en alleen als beide nullzijn.
  • Als de delegaten verschillende runtime-typen hebben, zijn ze nooit gelijk.
  • Als beide gemachtigden een aanroeplijst hebben (§20.2), zijn deze instanties gelijk als en alleen als hun aanroeplijsten dezelfde lengte hebben en elke vermelding in de aanroeplijst gelijk is (zoals hieronder gedefinieerd) aan de overeenkomstige vermelding in de lijst met aanroepen van de andere.

De volgende regels bepalen de gelijkheid van vermeldingen in de aanroeplijst:

  • Als twee vermeldingen in de aanroeplijst beide verwijzen naar dezelfde statische methode, zijn de vermeldingen gelijk.
  • Als twee vermeldingen in de aanroeplijst beide verwijzen naar dezelfde niet-statische methode op hetzelfde doelobject (zoals gedefinieerd door de operatoren voor verwijzings gelijkheid), zijn de vermeldingen gelijk.
  • Vermeldingen in de aanroeplijst die zijn geproduceerd uit de evaluatie van semantisch identieke anonieme functies (§12.19) met dezelfde (mogelijk lege) set vastgelegde buitenste variabelenexemplaren zijn toegestaan (maar niet vereist) gelijk te zijn.

Als de overbelastingresolutie van operatoren resulteert in een gedelegeerde gelijkheidsoperator, en de bindingstijdtypen van beide operanden gedelegeerdentypen zijn, zoals beschreven is in §20 in plaats van in System.Delegate, en er geen identiteitsconversie is tussen de bindingstype operandtypen, ontstaat er een bindingstijdfout.

Opmerking: deze regel voorkomt vergelijkingen die nooit rekening kunnen houden met niet-null-waarden omdat ze verwijzingen zijn naar exemplaren van verschillende typen gedelegeerden. eindnotitie

12.12.10 Gelijkheidsoperatoren tussen nullable waardetypes en de null-literal

Met de operatoren == en != kan één operand een waarde zijn van een type null-waarde en de andere de letterlijke null zijn, zelfs als er geen vooraf gedefinieerde of door de gebruiker gedefinieerde operator (in niet-opgehaalde of opgeheven vorm) bestaat voor de bewerking.

Voor een bewerking van een van de formulieren

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

als x een expressie is van een nullbaar waardetype en de operator overbelastingsresolutie (§12.4.5) geen toepasselijke operator kan vinden, wordt het resultaat in plaats daarvan berekend op basis van de eigenschap HasValue van x. Met name worden de eerste twee formulieren omgezet in !x.HasValueen de laatste twee formulieren worden omgezet in x.HasValue.

12.12.11 Tuple-gelijkheidsoperators

De tuple gelijkheidsoperatoren worden paarsgewijs toegepast op de elementen van de tuple operanden in lexicale volgorde.

Als elke operand x en y van een operator == of != wordt geclassificeerd als tupel of als een waarde met een tupletype (§8.3.11), is de operator een tuple-gelijkheidsoperator.

Als een operand e als tuple wordt geclassificeerd, zijn de elementen e1...en de resultaten van het evalueren van de elementexpressies van de tuple-expressie. Anders, als e een waarde van een tupeltype is, zullen de elementen t.Item1...t.Itemn zijn, waarbij t het resultaat is van het evalueren van e.

De operanden x en y van een tuple-gelijkheidsoperator moeten dezelfde arity hebben, of er treedt een compilatietijdfout op. Voor elk paar elementen xi en yiis dezelfde gelijkheidsoperator van toepassing en wordt een resultaat van het type bool, dynamic, een type dat een impliciete conversie naar boolheeft, of een type dat de operatoren true en false definieert.

De tuple-gelijkheidsoperator x == y wordt als volgt geëvalueerd:

  • De linkeroperand x wordt geëvalueerd.
  • De rechteroperand y wordt geëvalueerd.
  • Voor elk paar elementen xi en yi in lexicale volgorde:
    • De operator xi == yi wordt geëvalueerd en het resultaat van het type bool wordt op de volgende manier verkregen:
      • Als de vergelijking een bool oplevert, is dat het resultaat.
      • Anders, als de vergelijking een dynamic oplevert, wordt de operator false er dynamisch op aangeroepen en wordt de resulterende bool-waarde met de logische negatieoperator (!) ontkend.
      • Anders wordt die conversie toegepast als het type van de vergelijking een impliciete conversie naar boolheeft.
      • Anders, als het type van de vergelijking een operator falseheeft, dan wordt die operator aangeroepen en wordt de resulterende bool-waarde geïnverteerd met de logische negatieoperator (!).
    • Als de resulterende boolfalseis, wordt er geen verdere evaluatie uitgevoerd en wordt het resultaat van de tuple-gelijkheidsoperator false.
  • Als alle elementvergelijkingen trueopleveren, wordt het resultaat van de tupel gelijkheidsoperator true.

De tuple-gelijkheidsoperator x != y wordt als volgt geëvalueerd:

  • De linkeroperand x wordt geëvalueerd.
  • De rechteroperand y wordt geëvalueerd.
  • Voor elk paar elementen xi en yi in lexicale volgorde:
    • De operator xi != yi wordt geëvalueerd en het resultaat van het type bool wordt op de volgende manier verkregen:
      • Als de vergelijking een bool oplevert, is dat het resultaat.
      • Anders, als de vergelijking een dynamic oplevert, wordt de operator true er dynamisch op aangeroepen en is de resulterende waarde bool het resultaat.
      • Anders wordt die conversie toegepast als het type van de vergelijking een impliciete conversie naar boolheeft.
      • Als het type van de vergelijking een operator trueheeft, wordt deze operator aangeroepen en is de resulterende waarde bool het resultaat.
    • Als de resulterende booltrueis, wordt er geen verdere evaluatie uitgevoerd en wordt het resultaat van de tuple-gelijkheidsoperator true.
  • Als alle elementvergelijkingen falseopleveren, wordt het resultaat van de tupel gelijkheidsoperator false.

12.12.12 De operator is

Er zijn twee vormen van de operator is. Een hiervan is de is-type operator, die een type aan de rechterzijde heeft. De andere is de is-pattern operator, die een patroon aan de rechterkant heeft.

De is-type operator

De is-type operator wordt gebruikt om te controleren of het runtimetype van een object compatibel is met een bepaald type. De controle wordt uitgevoerd tijdens runtime. Het resultaat van de bewerking E is T, waarbij E een expressie is en T een ander type is dan dynamic, is een Booleaanse waarde die aangeeft of E niet-null is en kan worden geconverteerd naar type T door een verwijzingsconversie, een boksconversie, een conversie voor uitpakken, een terugloopconversie of een uitgepakte conversie.

De bewerking wordt als volgt geëvalueerd:

  1. Als E een anonieme functie of methodegroep is, treedt er een compilatietijdfout op.
  2. Als E de letterlijke null is of als de waarde van Enullis, wordt het resultaat false.
  3. Anders:
  4. Laat R het runtimetype van Ezijn.
  5. Laat D als volgt worden afgeleid van R:
  6. Als R een null-waardetype is, is D het onderliggende type R.
  7. Anders wordt DR.
  8. Het resultaat is afhankelijk van D en T als volgt:
  9. Als T een verwijzingstype is, wordt het resultaat true als:
    • er bestaat een identiteitsconversie tussen D en T,
    • D is een verwijzingstype en een impliciete verwijzingsconversie van D tot T bestaat, of
    • Ofwel: D is een waardetype en een boksconversie van D naar T bestaat.
      Of: D is een waardetype en T is een interfacetype dat door Dwordt geïmplementeerd.
  10. Als T een null-waarde type is, is het resultaat true als D het onderliggende type van Tis.
  11. Als T een niet-null-waardetype is, wordt het resultaat true als D en T hetzelfde type zijn.
  12. Anders wordt het resultaat false.

Door de gebruiker gedefinieerde conversies worden niet meegenomen door de operator is.

Opmerking: als de operator is tijdens runtime wordt geëvalueerd, zijn alle typeargumenten vervangen en zijn er geen open typen (§8.4.3) om rekening mee te houden. eindnotitie

Opmerking: de operator is kan als volgt worden begrepen in termen van compileertijdtypen en conversies, waarbij C het type compilatietijd van Eis:

  • Als het compileertijdtype van e hetzelfde is als Tof als een impliciete verwijzingsconversie (§10.2.8), boksconversie (§10.2.9), wrapconversie (§10.6), of een expliciete uitgepakte conversie (§10.6) bestaat vanuit het compileertijdtype van E naar T:
    • Als C van een niet-null-waardetype is, wordt het resultaat van de bewerking true.
    • Anders is het resultaat van de bewerking gelijk aan het evalueren van E != null.
  • Anders, als een expliciete verwijzingsconversie (§10.3.5) of een unboxing conversie (§10.3.7) bestaat van C naar T, of als C of T een open type is (§8.4.3), dan worden runtimecontroles zoals hierboven uitgevoerd.
  • Anders is er geen referentie-, boks-, inpak- of uitpakconversie van E naar type T mogelijk, en het resultaat van de bewerking is false. Een compiler kan optimalisaties implementeren op basis van het type compileertijd.

eindnotitie

12.12.12.2 De is-patroon-operator

De is-patroonoperator wordt gebruikt om te controleren of de waarde die wordt berekend door een expressie overeenkomt met een bepaald patroon (§11). De controle wordt uitgevoerd tijdens runtime. Het resultaat van de is-patroon operator is waar als de waarde overeenkomt met het patroon, anders is het onwaar.

Voor een expressie van het formulier E is P, waarbij E een relationele expressie van het type T is en P een patroon is, is het een compilatiefout als een van de volgende bewaringen geldt:

  • E wijst geen waarde aan of heeft geen type.
  • Het patroon P is niet van toepassing (§11.2) op het type T.

12.12.13 De operator

De operator as wordt gebruikt om expliciet een waarde te converteren naar een bepaald verwijzingstype of null-waardetype. In tegenstelling tot een cast-expressie (§12.9.7), veroorzaakt de operator as nooit een uitzondering. Als de aangegeven conversie niet mogelijk is, wordt de resulterende waarde null.

In een operatie van de vorm E as Tmoet E een expressie zijn en T een verwijzingstype zijn, een typeparameter die bekend staat als een verwijzingstype, of een null-waarde type. Bovendien is ten minste één van de volgende waar, of op andere wijze treedt er een compilatietijdfout op:

  • Een identiteit (§10.2.2), impliciet nullable (§10.2.6), impliciete verwijzing (§10.2.8), boksen (§10.2.9), expliciet nullable (§10.3.4), expliciete verwijzing (§10.3.5), of wrapping (§8.3.12) conversie bestaat van E tot T.
  • Het type E of T is een open type.
  • E is de letterlijke waarde van null.

Als het type compilatietijd van E niet is dynamic, produceert de bewerking E as T hetzelfde resultaat als

E is T ? (T)(E) : (T)null

behalve dat E slechts eenmaal wordt geëvalueerd. Er kan worden verwacht dat een compiler E as T optimaliseert om maximaal één runtimetypecontrole uit te voeren in plaats van de twee runtimetypecontroles die worden geïmpliceerd door de bovenstaande uitbreiding.

Als het compilatietijdtype van Edynamicis, dan is, in tegenstelling tot de cast-operator, de operator as niet dynamisch gebonden (§12.3.3). Daarom is de uitbreiding in dit geval:

E is T ? (T)(object)(E) : (T)null

Houd er rekening mee dat sommige conversies, zoals door de gebruiker gedefinieerde conversies, niet mogelijk zijn met de operator as en in plaats daarvan moeten worden uitgevoerd met cast-expressies.

Voorbeeld: In het voorbeeld

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

de typeparameter T van G is een referentietype, omdat het de klassebeperking heeft. Het typeparameter U van H is echter niet; vandaar dat het gebruik van de operator as in H niet is toegestaan.

einde voorbeeld

12.13 Logische operators

12.13.1 Algemeen

De operators &, ^en | worden de logische operatoren genoemd.

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
    ;

Als een operand van een logische operator het type compileertijd heeft dynamic, is de expressie dynamisch gebonden (§12.3.3). In dit geval is het compile-tijdtype van de expressie dynamic, en zal de hieronder beschreven resolutie plaatsvinden tijdens de uitvoertijd met behulp van het uitvoertijdtype van die operanden die het compile-tijdtype dynamichebben.

Voor een werking van het formulier x «op» y, waarbij «op» een van de logische operatoren is, wordt overbelastingsresolutie (§12.4.5) toegepast om een specifieke operator-implementatie te selecteren. De operanden worden geconverteerd naar de parametertypen van de geselecteerde operator en het type van het resultaat is het retourtype van de operator.

De vooraf gedefinieerde logische operators worden beschreven in de volgende subclauses.

12.13.2 Logische operatoren voor gehele getallen

De vooraf gedefinieerde logische operatoren voor gehele getallen zijn:

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

De operator & berekent de bitsgewijze logische EN van de twee operanden, de operator | berekent de bitsgewijze logische OR van de twee operanden, en de operator ^ berekent de bitsgewijze logische exclusieve OR van de twee operanden. Er zijn geen overloopmogelijkheden bij deze bewerkingen.

Opgeheven (§12.4.8) vormen van de niet-opgeheven, vooraf gedefinieerde logische operatoren voor gehele getallen die hierboven zijn gedefinieerd, zijn ook vooraf gedefinieerd.

12.13.3 Logische operatoren voor enumeraties

Elk opsommingstype E impliciet de volgende vooraf gedefinieerde logische operators biedt:

E operator &(E x, E y);
E operator |(E x, E y);
E operator ^(E x, E y);

Het resultaat van het evalueren van x «op» y, waarbij x en y expressies zijn van een opsommingstype E met een onderliggend type Uen «op» een van de logische operatoren is, is precies hetzelfde als het evalueren van (E)((U)x «op» (U)y). Met andere woorden, de logische operatoren van het opsommingstype voeren de logische bewerking uit op het onderliggende type van de twee operanden.

Opgeheven (§12.4.8) vormen van de niet-opgeheven logische inventarisatieoperators die hierboven zijn gedefinieerd, zijn ook vooraf gedefinieerd.

12.13.4 Booleaanse logische operatoren

De vooraf gedefinieerde logische booleaanse operators zijn:

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

Het resultaat van x & y is true als zowel x als ytruezijn. Anders wordt het resultaat false.

Het resultaat van x | y is true als x of ytrueis. Anders wordt het resultaat false.

Het resultaat van x ^ y is true als xtrue is en yfalseis, of xfalse is en y is true. Anders wordt het resultaat false. Wanneer de operanden van het type boolzijn, berekent de operator ^ hetzelfde resultaat als de operator !=.

12.13.5 Nullable Booleaanse waarden & en | operatoren

Het nulbare booleaanse type bool? kan drie waarden, true, falseen null, vertegenwoordigen.

Net als bij de andere binaire operatoren zijn opgeheven vormen van de logische operatoren & en | (§12.13.4) ook vooraf gedefinieerd:

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

De semantiek van de opgeheven & en | operatoren worden gedefinieerd door de volgende tabel:

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

Opmerking: het bool? type is conceptueel vergelijkbaar met het type met drie waarden dat wordt gebruikt voor Boole-expressies in SQL. De bovenstaande tabel volgt dezelfde semantiek als SQL, terwijl het toepassen van de regels van §12.4.8 op de & en | operators dat niet zou doen. De regels van §12.4.8 bieden al SQL-achtige semantiek voor de opgetilde ^ operator. eindnotitie

12.14 Voorwaardelijke logische operators

12.14.1 Algemeen

De operators && en || worden de voorwaardelijke logische operators genoemd. Ze worden ook wel de kortsluitende logische operatoren genoemd.

conditional_and_expression
    : inclusive_or_expression
    | conditional_and_expression '&&' inclusive_or_expression
    ;

conditional_or_expression
    : conditional_and_expression
    | conditional_or_expression '||' conditional_and_expression
    ;

De operators && en || zijn voorwaardelijke versies van de operators & en |:

  • De bewerking x && y komt overeen met de bewerking x & y, behalve dat y alleen wordt geëvalueerd als x niet is false.
  • De bewerking x || y komt overeen met de bewerking x | y, behalve dat y alleen wordt geëvalueerd als x niet is true.

Opmerking: de reden dat kortsluiting gebruikmaakt van de voorwaarden 'niet waar' en 'niet onwaar' is om door de gebruiker gedefinieerde voorwaardelijke operators in staat te stellen te definiëren wanneer kortsluiting van toepassing is. Door de gebruiker gedefinieerde typen kunnen een status hebben waarin operator truefalse retourneert en operator falsefalseretourneert. In die gevallen zouden noch && noch || kortsluiting hebben. eindnotitie

Als een operand van een voorwaardelijke logische operator het type compileertijd heeft dynamic, is de expressie dynamisch gebonden (§12.3.3). In dit geval is het compile-tijdtype van de expressie dynamic, en zal de hieronder beschreven resolutie plaatsvinden tijdens de uitvoertijd met behulp van het uitvoertijdtype van die operanden die het compile-tijdtype dynamichebben.

Een bewerking van de vorm x && y of x || y wordt verwerkt door overbelastingsresolutie toe te passen (§12.4.5) alsof de bewerking als x & y of x | yis geschreven. Dan

  • Als overbelastingresolutie geen enkele beste operator vindt, of als overbelastingresolutie een van de vooraf gedefinieerde logische operatoren voor gehele getallen of logische Boole-operatoren (§12.13.5) selecteert, treedt er een bindingstijdfout op.
  • Als de geselecteerde operator een van de vooraf gedefinieerde booleaanse logische operatoren (§12.13.4) is, wordt de bewerking verwerkt zoals beschreven in §12.14.2.
  • Anders is de geselecteerde operator een door de gebruiker gedefinieerde operator en wordt de bewerking verwerkt zoals beschreven in §12.14.3.

Het is niet mogelijk om de voorwaardelijke logische operators rechtstreeks te overbelasten. Omdat de voorwaardelijke logische operators echter worden geëvalueerd in termen van de reguliere logische operators, worden overbelastingen van de reguliere logische operators, met bepaalde beperkingen, ook beschouwd als overbelasting van de voorwaardelijke logische operators. Dit wordt verder beschreven in §12.14.3.

12.14.2 Booleaanse voorwaardelijke logische operators

Wanneer de operanden van && of || van het type boolzijn, of wanneer de operanden van typen zijn die geen toepasselijke operator & of operator |definiëren, maar impliciete conversies naar booldefiniëren, wordt de bewerking als volgt verwerkt:

  • De bewerking x && y wordt geëvalueerd als x ? y : false. Met andere woorden, x wordt eerst geëvalueerd en geconverteerd naar type bool. Als x vervolgens is true, wordt y geëvalueerd en geconverteerd naar het type boolen wordt dit het resultaat van de bewerking. Anders wordt het resultaat van de bewerking false.
  • De bewerking x || y wordt geëvalueerd als x ? true : y. Met andere woorden, x wordt eerst geëvalueerd en geconverteerd naar type bool. Als x vervolgens is true, wordt het resultaat van de bewerking true. Anders wordt y geëvalueerd en geconverteerd naar het type boolen wordt dit het resultaat van de bewerking.

12.14.3 Door de gebruiker gedefinieerde conditionele logische operators

Wanneer de operanden van && of || van typen zijn die een door de gebruiker gedefinieerde operator & of operator |declareren, gelden beide van de volgende waarden, wanneer T het type is waarin de geselecteerde operator wordt gedeclareerd:

  • Het retourtype en het type van elke parameter van de geselecteerde operator worden T. Met andere woorden, de operator berekent de logische AND of de logische OR van twee operanden van het type Ten retourneert een resultaat van het type T.
  • T bevat verklaringen van operator true en operator false.

Er treedt een bindingstijdfout op als niet aan een van deze vereisten wordt voldaan. Anders wordt de &&- of ||-bewerking geëvalueerd door de door de gebruiker gedefinieerde operator true of operator false te combineren met de geselecteerde door de gebruiker gedefinieerde operator:

  • De bewerking x && y wordt geëvalueerd als T.false(x) ? x : T.&(x, y), waarbij T.false(x) een aanroep is van de operator false die in Tis gedeclareerd en T.&(x, y) een aanroep van de geselecteerde operator &is. Met andere woorden, x wordt eerst geëvalueerd en operator false wordt aangeroepen op het resultaat om te bepalen of x zeker onwaar is. Als x dan zeker onwaar is, is het resultaat van de bewerking de waarde die eerder is berekend voor x. Anders wordt y geëvalueerd en wordt de geselecteerde operator & aangeroepen op de waarde die eerder is berekend voor x en de waarde die is berekend voor y om het resultaat van de bewerking te produceren.
  • De bewerking x || y wordt geëvalueerd als T.true(x) ? x : T.|(x, y), waarbij T.true(x) een aanroep is van de operator true die in Tis gedeclareerd en T.|(x, y) een aanroep van de geselecteerde operator |is. Met andere woorden, x wordt eerst geëvalueerd en operator true wordt aangeroepen op het resultaat om te bepalen of x zeker waar is. Als x dan zeker waar is, is het resultaat van de bewerking de waarde die eerder is berekend voor x. Anders wordt y geëvalueerd en wordt de geselecteerde operator | aangeroepen op de waarde die eerder is berekend voor x en de waarde die is berekend voor y om het resultaat van de bewerking te produceren.

In een van deze bewerkingen wordt de expressie die door x wordt gegeven slechts eenmaal geëvalueerd, en wordt de expressie die door y wordt gegeven, ofwel niet geëvalueerd of precies één keer geëvalueerd.

12.15 De null-coalescing-operator

De ??-operator wordt de null-coalescing-operator genoemd.

null_coalescing_expression
    : conditional_or_expression
    | conditional_or_expression '??' null_coalescing_expression
    | throw_expression
    ;

In een null-coalitie-expressie van de vorm a ?? b, als a nietnullis, wordt het resultaat a; anders wordt het resultaat b. De bewerking evalueert b alleen wanneer anullis.

De operator null-samenvoegen is rechts-associatief, wat betekent dat bewerkingen van rechts naar links worden gegroepeerd.

Voorbeeld: een expressie van het formulier a ?? b ?? c wordt geëvalueerd als a ?? (b ?? c). In het algemeen retourneert een expressie met de vorm E1 ?? E2 ?? ... ?? EN de eerste van de operanden die niet-nullis, of null als alle operanden nullzijn. einde voorbeeld

Het type expressie a ?? b is afhankelijk van welke impliciete conversies beschikbaar zijn voor de operanden. In volgorde van voorkeur is het type a ?? bA₀, Aof B, waarbij A het type a is (mits a een type heeft), is B het type b(mits b een type heeft) en is A₀ het onderliggende type A als A een type null-waarde is of anders A. In het bijzonder wordt a ?? b als volgt verwerkt:

  • Als A bestaat en geen niet-nullbaar waarde- of referentietype is, treedt er een compilatietijdfout op.
  • Als A bestaat en b een dynamische expressie is, wordt het resultaattype dynamic. Tijdens runtime wordt a eerst geëvalueerd. Als a niet is null, wordt a geconverteerd naar dynamicen wordt dit het resultaat. Anders wordt b geëvalueerd en dit wordt het resultaat.
  • Anders, als A bestaat en een null-waardetype is en er een impliciete conversie bestaat van b naar A₀, is het resultaattype A₀. Tijdens runtime wordt a eerst geëvalueerd. Als a niet nullis, wordt a naar type A₀uitgepakt, en wordt dit het resultaat. Anders wordt b geëvalueerd en geconverteerd naar het type A₀, en wordt dit het resultaat.
  • Als A bestaat en er een impliciete conversie bestaat van b naar A, wordt het resultaattype A. Tijdens runtime wordt a eerst geëvalueerd. Als a niet is null, wordt a het resultaat. Anders wordt b geëvalueerd en geconverteerd naar het type A, en wordt dit het resultaat.
  • Anders, als A bestaat en het een nullbare waarde is, b een type B heeft en er een impliciete conversie bestaat van A₀ naar B, is het resultaattype B. Tijdens runtime wordt a eerst geëvalueerd. Als a niet nullis, wordt a uitgepakt naar type A₀, geconverteerd naar type B, en dit wordt vervolgens het resultaat. Anders wordt b geëvalueerd en wordt het resultaat.
  • Als b een type B heeft en er een impliciete conversie bestaat van a tot B, wordt het resultaattype B. Tijdens runtime wordt a eerst geëvalueerd. Als a niet is null, wordt a geconverteerd naar het type Ben wordt dit het resultaat. Anders wordt b geëvalueerd en wordt het resultaat.

Anders zijn a en b niet compatibel en treedt er een compilatiefout op.

12.16 De operator voor het opwerpen van expressies

throw_expression
    : 'throw' null_coalescing_expression
    ;

Een throw_expression genereert de waarde die wordt geproduceerd door de null_coalescing_expressionte evalueren. De expressie moet impliciet omgezet kunnen worden naar System.Exceptionen het resultaat van de evaluatie van de expressie wordt geconverteerd naar System.Exception voordat deze wordt gegooid. Het gedrag tijdens de evaluatie van een throw-expressie is hetzelfde als opgegeven voor een throw-instructie (§13.10.6).

Een throw_expression heeft geen type. Een throw_expression is om te zetten naar elk type via een impliciete throw-conversie.

Een expressie die genereert, vindt alleen plaats in de volgende syntactische contexten:

  • Als tweede of derde operand van een ternaire voorwaardelijke operator (?:).
  • Als de tweede operand van een null coalescing-operator (??).
  • Als de hoofdtekst van een expressie-bodied lambda of lid.

12.17 Declaratie-expressies

Een declaratie-expressie declareert een lokale variabele.

declaration_expression
    : local_variable_type identifier
    ;

local_variable_type
    : type
    | 'var'
    ;

De simple_name_ wordt ook beschouwd als een declaratie-expressie als een eenvoudige naamzoekactie geen bijbehorende declaratie heeft gevonden (§12.8.4). Wanneer deze wordt gebruikt als een declaratie-expressie, wordt _ een eenvoudige verwijderinggenoemd. Het is semantisch equivalent aan var _, maar is toegestaan op meer plaatsen.

Een declaratie-expressie vindt alleen plaats in de volgende syntactische contexten:

  • Als outargument_value in een argumentenlijst.
  • Als een eenvoudige wegwerpwaarde _ die deel uitmaakt van de linkerkant van een eenvoudige toewijzing (§12.21.2).
  • Als een tuple_element in een of meer recursief geneste tuple_expressions, waarbij de buitenste van deze de linkerzijde vormt van een deconstructerende toewijzing. Een deconstruction_expression geeft aanleiding tot declaratie-expressies in deze positie, ook al zijn de declaratie-expressies niet syntactisch aanwezig.

Opmerking: dit betekent dat een declaratie-expressie niet tussen haakjes kan worden geplaatst. eindnotitie

Het is een fout als er naar een impliciet getypte variabele wordt gerefereerd binnen de argument_list waarin deze met een declaration_expression is gedeclareerd.

Het is een fout voor een variabele die is gedeclareerd met een declaration_expression waarnaar moet worden verwezen binnen de deconstructing-toewijzing waar deze zich voordoet.

Een declaratie-expressie die een simpele weglating is of waarbij de local_variable_type de identifier var is, wordt geclassificeerd als een impliciet getypte variabele. De expressie heeft geen type en het type van de lokale variabele wordt als volgt afgeleid op basis van de syntactische context:

  • In een argument_list is het afgeleide type van de variabele het gedeclareerde type van de bijbehorende parameter.
  • Als de linkerzijde van een eenvoudige toewijzing is het afgeleide type van de variabele het type van de rechterkant van de toewijzing.
  • In een tuple_expression aan de linkerkant van een eenvoudige toewijzing is het afgeleide type van de variabele het type van het overeenkomstige tuple-element aan de rechterkant (na deconstructie) van de toewijzing.

Anders wordt de declaratie-expressie geclassificeerd als een expliciet getypt variabel, en het type van de expressie en de gedeclareerde variabele moeten worden aangegeven door de local_variable_type.

Een declaratie-expressie met de id _ is een verwijdering (§9.2.9.2) en introduceert geen naam voor de variabele. Een declaratie-expressie met een andere identifier dan _ introduceert die naam in de dichtstbijzijnde omgeving voor lokale variabeleverklaringen (§7.3).

voorbeeld van:

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

De declaratie van s1 toont zowel expliciete als impliciet getypte declaratie-expressies. Het afgeleide type van b1 is bool omdat het het type van de bijbehorende uitvoerparameter in M1is. De daaropvolgende WriteLine heeft toegang tot i1 en b1, die zijn geïntroduceerd in het ingesloten bereik.

De declaratie van s2 toont een poging om i2 te gebruiken in de geneste aanroep naar M, wat niet is toegestaan, omdat de verwijzing optreedt in de lijst met argumenten waarin i2 is gedeclareerd. Aan de andere kant is de verwijzing naar b2 in het laatste argument toegestaan, omdat deze plaatsvindt na het einde van de geneste lijst met argumenten waar b2 is gedeclareerd.

De declaratie van s3 toont het gebruik van zowel impliciet als expliciet getypte declaratie-expressies die worden weggegooid. Omdat discards geen benoemde variabele declareren, zijn meerdere verschijningen van de identificator _ toegestaan.

(int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5);

In dit voorbeeld ziet u het gebruik van impliciete en expliciet-getype declaratie-expressies voor zowel variabelen als wegwerpwaarden in een deconstruerende toewijzing. De simple_name_ is gelijk aan var _ wanneer er geen declaratie van _ wordt gevonden.

void M1(out int i) { ... }

void M2(string _)
{
    M1(out _);      // Error: `_` is a string
    M1(out var _);
}

In deze voorbeelden ziet u het gebruik van var _ om impliciet getypte verwijdering te bieden wanneer _ niet beschikbaar is, omdat er een variabele in het bereik insluiten wordt toegewezen.

einde voorbeeld

12.18 Voorwaardelijke operator

De operator ?: wordt de voorwaardelijke operator genoemd. Het wordt soms ook wel de ternaire operator genoemd.

conditional_expression
    : null_coalescing_expression
    | null_coalescing_expression '?' expression ':' expression
    | null_coalescing_expression '?' 'ref' variable_reference ':'
      'ref' variable_reference
    ;

Een throw-expressie (§12.16) is niet toegestaan in een voorwaardelijke operator als ref aanwezig is.

Een voorwaardelijke expressie van het formulier b ? x : y evalueert eerst de voorwaarde b. Als b vervolgens is true, wordt x geëvalueerd en wordt het resultaat van de bewerking. Anders wordt y geëvalueerd en is dit het resultaat van de bewerking. Een voorwaardelijke expressie evalueert nooit zowel x als y.

De voorwaardelijke operator is rechts-associatief, wat betekent dat bewerkingen van rechts naar links worden gegroepeerd.

Voorbeeld: een expressie van het formulier a ? b : c ? d : e wordt geëvalueerd als a ? b : (c ? d : e). einde voorbeeld

De eerste operand van de operator ?: moet een expressie zijn die impliciet kan worden geconverteerd naar boolof een expressie van een type dat operator trueimplementeert. Als aan geen van deze vereisten wordt voldaan, treedt er een compilatietijdfout op.

Als ref aanwezig is:

  • Er bestaat een identiteitsconversie tussen de typen van de twee variable_references en het type van het resultaat kan een van beide typen zijn. Als een van de typen dynamicis, geeft type deductie de voorkeur aan dynamic (§8,7). Als een van beide typen een tupeltype is (§8.3.11), bevat de deductie de elementnamen wanneer de elementnamen in dezelfde rangorde overeenkomen met beide tuples.
  • Het resultaat is een variabele verwijzing, die kan worden geschreven als beide variable_references schrijfbaar zijn.

Opmerking: wanneer ref aanwezig is, retourneert de conditional_expression een variabelereferentie die kan worden toegewezen aan een verwijzingsvariabele met behulp van de = ref-operator of doorgegeven als referentie-/invoer-/uitvoerparameter. eindnotitie

Als ref niet aanwezig is, bepalen de tweede en derde operanden, x en y, van de operator ?: het type voorwaardelijke expressie:

  • Als x type X heeft en y dan Y heeft,
    • Als er een identiteitsconversie bestaat tussen X en Y, is het resultaat het beste gemeenschappelijke type van een set expressies (§12.6.3.15). Als een van de typen dynamicis, geeft type deductie de voorkeur aan dynamic (§8,7). Als een van beide typen een tupeltype is (§8.3.11), bevat de deductie de elementnamen wanneer de elementnamen in dezelfde rangorde overeenkomen met beide tuples.
    • Als er anders een impliciete conversie (§10,2) bestaat van X tot Y, maar niet van Y tot X, is Y het type van de voorwaardelijke expressie.
    • Als er anders een impliciete opsommingsconversie (§10.2.4) bestaat van X tot Y, is Y het type voorwaardelijke expressie.
    • Als er anders een impliciete opsommingsconversie (§10.2.4) bestaat van Y tot X, is X het type voorwaardelijke expressie.
    • Als er anders een impliciete conversie (§10,2) bestaat van Y tot X, maar niet van X tot Y, is X het type van de voorwaardelijke expressie.
    • Anders kan geen expressietype worden bepaald en treedt er een compilatietijdfout op.
  • Als slechts één van x en y een type heeft, en zowel x als y impliciet kunnen worden omgezet in dat type, is dat het type van de voorwaardelijke expressie.
  • Anders kan geen expressietype worden bepaald en treedt er een compilatietijdfout op.

De runtimeverwerking van een voorwaardelijke ref-expressie van het formulier b ? ref x : ref y bestaat uit de volgende stappen:

  • Eerst wordt b geëvalueerd en wordt de bool waarde van b bepaald:
    • Als er een impliciete conversie van het type b naar bool bestaat, wordt deze impliciete conversie uitgevoerd om een bool waarde te produceren.
    • Anders wordt de operator true die door het type b wordt bepaald aangeroepen om een bool-waarde te produceren.
  • Als de bool waarde die in de bovenstaande stap wordt geproduceerd, trueis, wordt x geëvalueerd en wordt de resulterende variabelereferentie het resultaat van de voorwaardelijke expressie.
  • Anders wordt y geëvalueerd en wordt de resulterende variabelereferentie het resultaat van de voorwaardelijke expressie.

De runtimeverwerking van een voorwaardelijke expressie van het formulier b ? x : y bestaat uit de volgende stappen:

  • Eerst wordt b geëvalueerd en wordt de bool waarde van b bepaald:
    • Als er een impliciete conversie van het type b naar bool bestaat, wordt deze impliciete conversie uitgevoerd om een bool waarde te produceren.
    • Anders wordt de operator true die door het type b wordt bepaald aangeroepen om een bool-waarde te produceren.
  • Als de bool waarde die in de bovenstaande stap wordt geproduceerd, trueis, wordt x geëvalueerd en geconverteerd naar het type voorwaardelijke expressie. Dit wordt het resultaat van de voorwaardelijke expressie.
  • Anders wordt y geëvalueerd en geconverteerd naar het type voorwaardelijke expressie. Dit wordt het resultaat van de voorwaardelijke expressie.

12.19 Anonieme functie-expressies

12.19.1 Algemeen

Een anonieme functie is een expressie die een 'in-line'-methodedefinitie vertegenwoordigt. Een anonieme functie heeft op zichzelf geen waarde of type, maar kan worden omgezet naar een compatibel delegate- of expressieboomtype. De evaluatie van een anonieme-functieconversie is afhankelijk van het doeltype van de conversie: als het een gedelegeerdetype is, wordt de conversie geëvalueerd naar een gedelegeerde waarde die verwijst naar de methode die door de anonieme functie wordt gedefinieerd. Als het een expressiestructuurtype is, evalueert de conversie naar een expressiestructuur die de structuur van de methode voorstelt als objectstructuur.

Opmerking: Om historische redenen zijn er twee syntactische varianten van anonieme functies, namelijk lambda_expressions en anonymous_method_expressions. Voor vrijwel alle doeleinden zijn lambda_expressions beknopter en expressief dan anonymous_method_expressions, die in de taal blijven voor achterwaartse compatibiliteit. eindnotitie

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
    ;

Bij de erkenning van een anonymous_function_body indien zowel de null_conditional_invocation_expression als de expressie alternatieven van toepassing zijn, wordt de vroegere gekozen.

Opmerking: de overlappende en prioriteit tussen deze alternatieven is uitsluitend bedoeld voor beschrijvend gemak; de grammaticaregels kunnen worden uitgewerkt om de overlapping te verwijderen. ANTLR en andere grammaticasystemen gebruiken hetzelfde gemak, zodat anonymous_function_body de opgegeven semantiek automatisch heeft. eindnotitie

Opmerking: bij behandeling als een expressie, zou een syntactische vorm zoals x?.M() een fout zijn als het resultaattype van Mvoid is (§12.8.13). Maar wanneer het wordt behandeld als een null_conditional_invocation_expression, mag het resultaattype voidzijn. eindnotitie

Voorbeeld: het resultaattype van List<T>.Reverse is void. In de volgende code is de hoofdtekst van de anonieme expressie een null_conditional_invocation_expression, dus het is geen fout.

Action<List<int>> a = x => x?.Reverse();

einde voorbeeld

De operator => heeft dezelfde prioriteit als toewijzing (=) en is rechts-associatief.

Een anonieme functie met de async modifier is een asynchrone functie en volgt de regels die worden beschreven in §15.15.

De parameters van een anonieme functie in de vorm van een lambda_expression kunnen expliciet of impliciet worden getypt. In een expliciet getypte parameterlijst wordt het type van elke parameter expliciet vermeld. In een impliciet getypte parameterlijst worden de typen van de parameters afgeleid van de context waarin de anonieme functie zich voordoet, met name wanneer de anonieme functie wordt geconverteerd naar een compatibel gedelegeerd type of expressiestructuurtype, dat type de parametertypen levert (§10.7).

In een lambda_expression met één impliciet getypte parameter kunnen de haakjes worden weggelaten uit de parameterlijst. Met andere woorden, een anonieme functie van het formulier

( «param» ) => «expr»

kan worden afgekort tot

«param» => «expr»

De parameterlijst van een anonieme functie in de vorm van een anonymous_method_expression is optioneel. Indien opgegeven, worden de parameters expliciet getypt. Als dat niet het geval is, wordt de anonieme functie omgezet naar een gemachtigde met een parameterlijst die geen uitvoerparameters bevat.

Een blok hoofdtekst van een anonieme functie is altijd bereikbaar (§13.2).

voorbeeld van: hieronder volgen enkele voorbeelden van anonieme functies:

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

einde voorbeeld

Het gedrag van lambda_expressionen anonymous_method_expressions is hetzelfde, met uitzondering van de volgende punten:

  • anonymous_method_expressionstaan toe dat de parameterlijst volledig wordt weggelaten, waardoor conversie naar delegeringstypen van een willekeurige lijst met waardeparameters mogelijk wordt.
  • lambda_expressiontoestaan dat parametertypen worden weggelaten en afgeleid, terwijl anonymous_method_expressions vereisen dat parametertypen expliciet worden vermeld.
  • De hoofdtekst van een lambda_expression kan een expressie of een blok zijn, terwijl de hoofdtekst van een anonymous_method_expression een blok moet zijn.
  • Alleen lambda_expressionhebben conversies naar compatibele expressiestructuurtypen (§8,6).

12.19.2 Anonieme functiehandtekeningen

De anonymous_function_signature van een anonieme functie definieert de namen en eventueel de typen parameters voor de anonieme functie. Het bereik van de parameters van de anonieme functie is de anonymous_function_body (§7,7). Samen met de parameterlijst (indien gegeven) vormt de anonieme methode-body een declaratieruimte (§7.3). Het is dus een compilatiefout voor de naam van een parameter van de anonieme functie die overeenkomt met de naam van een lokale variabele, lokale constante of parameter waarvan het bereik de anonymous_method_expression of lambda_expressionbevat.

Als een anonieme functie een explicit_anonymous_function_signatureheeft, is de set van compatibele gedelegeerde typen en expressiestructuurtypen beperkt tot die welke dezelfde parametertypen en modifiers in dezelfde volgorde hebben (§10,7). In tegenstelling tot methodegroepconversies (§10,8), wordt contravariantie van anonieme functieparametertypen niet ondersteund. Als een anonieme functie geen anonymous_function_signatureheeft, wordt de set compatibele typen gemachtigden en expressiestructuurtypen beperkt tot de typen met geen uitvoerparameters.

Houd er rekening mee dat een anonymous_function_signature geen kenmerken of een parametermatrix kan bevatten. Niettemin kan een anonymous_function_signature compatibel zijn met een gemachtigdentype waarvan de parameterlijst een parametermatrix bevat.

Houd er ook rekening mee dat de conversie naar een expressiestructuurtype, zelfs als dit compatibel is, tijdens het compileren nog steeds kan mislukken (§8.6).

12.19.3 Anonieme functielichamen

De hoofdtekst (expressie of blok) van een anonieme functie is onderworpen aan de volgende regels:

  • Als de anonieme functie een handtekening bevat, zijn de parameters die zijn opgegeven in de handtekening beschikbaar in de hoofdtekst. Als de anonieme functie geen handtekening heeft, kan deze worden geconverteerd naar een gemachtigde of expressietype met parameters (§10.7), maar de parameters kunnen niet worden geopend in de hoofdtekst.
  • Met uitzondering van by-reference-parameters die zijn opgegeven in de signatuur (indien aanwezig) van de dichtstbijzijnde anonieme functie, is het een compilatiefout als de functiebody toegang probeert te krijgen tot een by-reference-parameter.
  • Behalve voor parameters die zijn opgegeven in de signatuur (indien aanwezig) van de dichtstbijzijnde anonieme functie, is het een compilatiefout als de body probeert toegang te krijgen tot een parameter van een type ref struct.
  • Wanneer het type this een structtype is, is het een compilatiefout voor de code om toegang te krijgen tot this. Dit is waar of de toegang expliciet is (zoals in this.x) of impliciet (zoals in x waar x een exemplaarlid van de struct is). Deze regel verbiedt alleen dergelijke toegang en heeft geen invloed op het feit of het opzoeken van leden resulteert in een lid van de struct.
  • Het lichaam heeft toegang tot de buitenste variabelen (§12.19.6) van de anonieme functie. Toegang tot een buitenste variabele verwijst naar het exemplaar van de variabele die actief is op het moment dat de lambda_expression of anonymous_method_expression wordt geëvalueerd (§12.19.7).
  • Het is een compilatiefout als de hoofdtekst een goto-instructie, een break-instructie of een continue-instructie bevat, waarvan het doel zich buiten de hoofdtekst bevindt of binnen de hoofdtekst van een ingesloten anonieme functie.
  • Een return-instructie in de hoofdtekst retourneert de controle van een aanroep van de dichtstbijzijnde omsluitende anonieme functie, niet van de omsluitende functielid.

Het is expliciet niet gespecificeerd of er een andere manier is om het blok van een anonieme functie uit te voeren dan via evaluatie en aanroep van de lambda_expression of anonymous_method_expression. Een compiler kan er met name voor kiezen om een anonieme functie te implementeren door een of meer benoemde methoden of typen te synthetiseren. De namen van dergelijke gesynthetiseerde elementen moeten van een vorm zijn die gereserveerd is voor compilergebruik (§6.4.3).

12.19.4 Overbelastingsresolutie

Anonieme functies in een lijst met argumenten nemen deel aan typedeductie en overbelastingsresolutie. Raadpleeg §12.6.3 en §12.6.4 voor de exacte regels.

voorbeeld: in het volgende voorbeeld ziet u het effect van anonieme functies op overbelastingsresolutie.

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

De klasse ItemList<T> heeft twee Sum methoden. Elk heeft een selector argument, waarmee de waarde wordt geëxtraheerd die moet worden opgeteld uit een lijstitem. De geëxtraheerde waarde kan een int of een double zijn en de resulterende som is eveneens een int of een double.

De Sum methoden kunnen bijvoorbeeld worden gebruikt voor het berekenen van sommen uit een lijst met detailregels in een volgorde.

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

In de eerste aanroep van orderDetails.Sumzijn beide Sum methoden van toepassing omdat de anonieme functie d => d.UnitCount compatibel is met zowel Func<Detail,int> als Func<Detail,double>. Overbelastingsresolutie kiest echter de eerste Sum methode omdat de conversie naar Func<Detail,int> beter is dan de conversie naar Func<Detail,double>.

In de tweede aanroep van orderDetails.Sumis alleen de tweede Sum methode van toepassing omdat de anonieme functie d => d.UnitPrice * d.UnitCount een waarde van het type doubleproduceert. Overbelastingsresolutie kiest dus de tweede Sum methode voor die aanroep.

einde voorbeeld

12.19.5 Anonieme functies en dynamische binding

Een anonieme functie kan geen ontvanger, argument of operand van een dynamisch gebonden bewerking zijn.

12.19.6 Buitenste variabelen

12.19.6.1 Algemeen

Een lokale variabele, waardeparameter of parametermatrix waarvan het bereik de lambda_expression of anonymous_method_expression bevat, wordt een buitenste variabele van de anonieme functie genoemd. In een exemplaarfunctielid van een klasse wordt de waarde beschouwd als een waardeparameter en is dit een buitenste variabele van een anonieme functie die zich in het functielid bevindt.

12.19.6.2 Vastgelegde buitenste variabelen

Wanneer naar een buitenste variabele wordt verwezen door een anonieme functie, wordt gezegd dat de buitenste variabele is vastgelegd door de anonieme functie. Normaal gesproken is de levensduur van een lokale variabele beperkt tot de uitvoering van het blok of de instructie waaraan deze is gekoppeld (§9.2.9.1). De levensduur van een vastgelegde buitenste variabele wordt echter ten minste verlengd totdat de gedelegeerde of expressiestructuur die is gemaakt op basis van de anonieme functie in aanmerking komt voor garbagecollection.

Voorbeeld: In het voorbeeld

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

de lokale variabele x wordt vastgelegd door de anonieme functie, en de levensduur van x wordt ten minste verlengd totdat de gedelegeerde die is geretourneerd uit F in aanmerking komt voor afvalinzameling. Aangezien elke aanroep van de anonieme functie op hetzelfde exemplaar van xwerkt, is de uitvoer van het voorbeeld:

1
2
3

einde voorbeeld

Wanneer een lokale variabele of een waardeparameter wordt vastgelegd door een anonieme functie, wordt de lokale variabele of parameter niet langer beschouwd als een vaste variabele (§23.4), maar wordt in plaats daarvan beschouwd als een verplaatsbare variabele. Vastgelegde buitenste variabelen kunnen echter niet worden gebruikt in een fixed instructie (§23.7), zodat het adres van een vastgelegde buitenste variabele niet kan worden genomen.

Opmerking: in tegenstelling tot een niet-captured variabele kan een vastgelegde lokale variabele tegelijkertijd worden blootgesteld aan meerdere threads van uitvoering. eindnotitie

12.19.6.3 Instantiering van lokale variabelen

Een lokale variabele wordt beschouwd als geïnstantieerd wanneer de uitvoering het bereik van de variabele invoert.

voorbeeld: wanneer de volgende methode bijvoorbeeld wordt aangeroepen, wordt de lokale variabele x drie keer geïnstantieerd en geïnitialiseerd, eenmaal voor elke herhaling van de lus.

static void F()
{
    for (int i = 0; i < 3; i++)
    {
        int x = i * 2 + 1;
        ...
    }
}

Het verplaatsen van de declaratie van x buiten de lus resulteert echter in één instantie van x:

static void F()
{
    int x;
    for (int i = 0; i < 3; i++)
    {
        x = i * 2 + 1;
        ...
    }
}

einde voorbeeld

Wanneer deze niet wordt vastgelegd, is er geen manier om precies te zien hoe vaak een lokale variabele wordt geïnstantieerd, omdat de levensduur van de instantiëringen niet aaneengesloten zijn, is het voor elke instantatie mogelijk om gewoon dezelfde opslaglocatie te gebruiken. Wanneer een anonieme functie echter een lokale variabele vastlegt, worden de gevolgen van instantiëring duidelijk.

voorbeeld: het voorbeeld

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

produceert de uitvoer:

1
3
5

Wanneer de declaratie van x echter buiten de lus wordt verplaatst:

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

de uitvoer is:

5
5
5

Houd er rekening mee dat een compiler mag (maar niet verplicht is) de drie instanties optimaliseren tot één gedelegeerde instantie (§10.7.2).

einde voorbeeld

Als een for-lus een iteratievariabele declareert, wordt die variabele zelf beschouwd als buiten de lus.

voorbeeld: dus als het voorbeeld wordt gewijzigd om de iteratievariabele zelf vast te leggen:

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

er wordt slechts één exemplaar van de iteratievariabele vastgelegd, waardoor de uitvoer wordt geproduceerd:

3
3
3

einde voorbeeld

Het is mogelijk dat anonieme functiedelegen bepaalde vastgelegde variabelen delen, maar nog afzonderlijke exemplaren van anderen hebben.

Voorbeeld: bijvoorbeeld als F wordt gewijzigd in

static D[] F()
{
    D[] result = new D[3];
    int x = 0;
    for (int i = 0; i < 3; i++)
    {
        int y = 0;
        result[i] = () => Console.WriteLine($"{++x} {++y}");
    }
    return result;
}

de drie afgevaardigden leggen hetzelfde exemplaar van x vast, maar afzonderlijke exemplaren van y, en het resultaat is:

1 1
2 1
3 1

einde voorbeeld

Afzonderlijke anonieme functies kunnen hetzelfde exemplaar van een buitenste variabele vastleggen.

voorbeeld: In het voorbeeld:

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 twee anonieme functies leggen hetzelfde exemplaar van de lokale variabele vast x, en ze kunnen dus 'communiceren' via die variabele. De uitvoer van het voorbeeld is:

5
10

einde voorbeeld

12.19.7 Evaluatie van anonieme functie-expressies

Een anonieme functie F wordt altijd geconverteerd naar een gemachtigde D of een expressiestructuurtype E, hetzij rechtstreeks of via de uitvoering van een expressie voor het maken van een gemachtigde new D(F). Deze conversie bepaalt het resultaat van de anonieme functie, zoals beschreven in §10,7.

Implementatievoorbeeld 12.19.8

Deze subclause is informatief.

In dit subclause wordt een mogelijke implementatie van anonieme functieconversies beschreven in termen van andere C#-constructies. De implementatie die hier wordt beschreven, is gebaseerd op dezelfde principes die door een commerciële C#-compiler worden gebruikt, maar het is niet de enige mogelijke implementatie. Er wordt slechts kort melding gemaakt van conversies naar expressiebomen, omdat hun exacte semantiek buiten het bereik van deze specificatie valt.

De rest van deze subclause geeft verschillende voorbeelden van code die anonieme functies met verschillende kenmerken bevat. Voor elk voorbeeld wordt een bijbehorende vertaling naar code opgegeven die alleen andere C#-constructies gebruikt. In de voorbeelden wordt aangenomen dat de identifier D het volgende gemachtigdentype vertegenwoordigt.

public delegate void D();

De eenvoudigste vorm van een anonieme functie is een functie die geen buitenste variabelen vastlegt:

delegate void D();

class Test
{
    static void F()
    {
        D d = () => Console.WriteLine("test");
    }
}

Dit kan worden vertaald naar een gemachtigde instantie die verwijst naar een door een compiler gegenereerde statische methode waarin de code van de anonieme functie wordt geplaatst:

delegate void D();

class Test
{
    static void F()
    {
        D d = new D(__Method1);
    }

    static void __Method1()
    {
        Console.WriteLine("test");
    }
}

In het volgende voorbeeld verwijst de anonieme functie naar de instantieleden van this:

delegate void D();

class Test
{
    int x;

    void F()
    {
        D d = () => Console.WriteLine(x);
    }
}

Dit kan worden vertaald naar een door compiler gegenereerde exemplaarmethode die de code van de anonieme functie bevat:

delegate void D();

class Test
{
   int x;

   void F()
   {
       D d = new D(__Method1);
   }

   void __Method1()
   {
       Console.WriteLine(x);
   }
}

In dit voorbeeld legt de anonieme functie een lokale variabele vast:

delegate void D();

class Test
{
    void F()
    {
        int y = 123;
        D d = () => Console.WriteLine(y);
    }
}

De levensduur van de lokale variabele moet nu worden verlengd tot ten minste de levensduur van de gemachtigde voor anonieme functies. Dit kan worden bereikt door de lokale variabele te 'hoisen' in een veld van een door compiler gegenereerde klasse. Instantiëring van de lokale variabele (§12.19.6.3) komt vervolgens overeen met het maken van een exemplaar van de door compiler gegenereerde klasse en het openen van de lokale variabele komt overeen met het openen van een veld in het exemplaar van de door compiler gegenereerde klasse. Bovendien wordt de anonieme functie een instantiemethode van de door de compiler gegenereerde klasse:

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

Ten slotte worden met de volgende anonieme functie this en twee lokale variabelen met verschillende levensduur vastgelegd:

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

Hier wordt een door compiler gegenereerde klasse gemaakt voor elk blok waarin de lokale bevolking wordt vastgelegd, zodat de lokale bevolking in de verschillende blokken onafhankelijke levensduur kan hebben. Een exemplaar van __Locals2, de door de compiler gegenereerde klasse voor het binnenste blok, bevat de lokale variabele z en een veld dat verwijst naar een exemplaar van __Locals1. Een exemplaar van __Locals1, de door compiler gegenereerde klasse voor het buitenste blok, bevat de lokale variabele y en een veld dat verwijst naar this van het omsluitende functielid. Met deze gegevensstructuren is het mogelijk om alle vastgelegde buitenste variabelen te bereiken via een exemplaar van __Local2, en de code van de anonieme functie kan dus worden geïmplementeerd als een instantiemethode van die klasse.

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

Dezelfde techniek die hier wordt toegepast om lokale variabelen vast te leggen, kan ook worden gebruikt bij het converteren van anonieme functies naar expressiestructuren: verwijzingen naar door de compiler gegenereerde objecten kunnen worden opgeslagen in de expressiestructuur en toegang tot de lokale variabelen kunnen worden weergegeven als veldtoegang op deze objecten. Het voordeel van deze aanpak is dat de "gelifte" lokale variabelen kunnen worden gedeeld tussen delegates en expressiebomen.

einde van informatieve tekst.

12.20 Query-expressies

12.20.1 Algemeen

Query-expressies een taalgebaseerde syntaxis bieden voor query's die vergelijkbaar zijn met relationele en hiërarchische querytalen zoals SQL en 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
    ;

Een query-expressie begint met een from-component en eindigt met een select- of group-component. De eerste from component kan worden gevolgd door nul of meer from, let, where, join of orderby componenten. Elke from clausule is een generator die een bereikvariabele introduceert die varieert over de elementen van een reeks. Elke let component introduceert een bereikvariabele die een waarde vertegenwoordigt die wordt berekend met behulp van eerdere bereikvariabelen. Elke where component is een filter dat items uitsluit van het resultaat. Elke join component vergelijkt de opgegeven sleutels van de bronreeks met sleutels van een andere reeks, wat overeenkomende paren oplevert. Elke orderby component herschikt items volgens de opgegeven criteria. De laatste select of group component geeft de vorm van het resultaat op in termen van de bereikvariabelen. Ten slotte kan een into component worden gebruikt om query's te 'spliceren' door de resultaten van één query te behandelen als een generator in een volgende query.

12.20.2 Dubbelzinnigheden in query-expressies

Query-expressies gebruiken een aantal contextuele trefwoorden (§6.4.4): ascending, by, descending, equals, from, group, into, join, let, on, orderby, select en where.

Om dubbelzinnigheden te voorkomen die kunnen ontstaan door het gebruik van deze identificatoren, zowel als trefwoorden als eenvoudige namen, worden deze identificatoren overal in een query-expressie als trefwoorden beschouwd, tenzij ze worden voorafgegaan door “@” (§6.4.4), in welk geval ze als identificatoren worden beschouwd. Voor dit doel is een query-expressie een expressie die begint met "fromid" gevolgd door een token behalve ";", "=" of ",".

12.20.3 Vertaling van query-expressie

12.20.3.1 Algemeen

De C#-taal geeft de uitvoeringssemantiek van query-expressies niet op. In plaats daarvan worden queryexpressies omgezet in aanroepen van methoden die voldoen aan het queryexpressiepatroon (§12.20.4). Queryexpressies worden met name omgezet in aanroepen van methoden met de naam Where, Select, SelectMany, Join, GroupJoin, OrderBy, OrderByDescending, ThenBy, ThenByDescending, GroupByen Cast. Deze methoden zijn naar verwachting voorzien van bepaalde handtekeningen en retourtypen, zoals beschreven in §12.20.4. Deze methoden kunnen exemplaarmethoden zijn van het object waarop query's worden uitgevoerd of uitbreidingsmethoden die extern zijn voor het object. Met deze methoden wordt de werkelijke uitvoering van de query geïmplementeerd.

De vertaling van query-expressies naar methode-aanroepen is een syntactische toewijzing die plaatsvindt voordat een typebinding of overbelastingsresolutie wordt uitgevoerd. Na de vertaling van query-expressies worden de resulterende methode-aanroepen verwerkt als reguliere methode-aanroepen. Hierdoor kunnen op zijn beurt compilatiefouten worden gedetecteerd. Deze foutvoorwaarden omvatten, maar zijn niet beperkt tot methoden die niet bestaan, argumenten van de verkeerde typen en algemene methoden waarbij typedeductie mislukt.

Een query-expressie wordt verwerkt door herhaaldelijk de volgende vertalingen toe te passen totdat er geen verdere reducties mogelijk zijn. De vertalingen worden weergegeven in volgorde van toepassing: in elke sectie wordt ervan uitgegaan dat de vertalingen in de voorgaande secties volledig zijn uitgevoerd en zodra deze zijn uitgeput, wordt een sectie later niet meer bekeken in de verwerking van dezelfde query-expressie.

Het is een compileertijdfout voor een query-expressie om een toewijzing op te nemen aan een bereikvariabele of het gebruik van een bereikvariabele als argument voor een verwijzings- of uitvoerparameter.

Bepaalde vertalingen injecteren bereikvariabelen met transparante identificaties aangeduid met *. Deze worden verder beschreven in §12.20.3.8.

12.20.3.2 Query-expressies met doorvoeringen

Een query-expressie met een vervolg na de hoofdtekst van de query

from «x1» in «e1» «b1» into «x2» «b2»

wordt omgezet in

from «x2» in ( from «x1» in «e1» «b1» ) «b2»

Bij de vertalingen in de volgende secties wordt ervan uitgegaan dat query's geen vervolgen hebben.

Voorbeeld: Het voorbeeld:

from c in customers
group c by c.Country into g
select new { Country = g.Key, CustCount = g.Count() }

wordt omgezet in:

from g in
   (from c in customers
   group c by c.Country)
select new { Country = g.Key, CustCount = g.Count() }

de definitieve vertaling is:

customers.
GroupBy(c => c.Country).
Select(g => new { Country = g.Key, CustCount = g.Count() })

einde voorbeeld

12.20.3.3 Expliciete typen voor bereikvariabelen

Een from-component waarmee expliciet een bereikvariabeletype wordt opgegeven

from «T» «x» in «e»

wordt omgezet in

from «x» in ( «e» ) . Cast < «T» > ( )

Een join-component waarmee expliciet een bereikvariabeletype wordt opgegeven

join «T» «x» in «e» on «k1» equals «k2»

wordt omgezet in

join «x» in ( «e» ) . Cast < «T» > ( ) on «k1» equals «k2»

Bij de vertalingen in de volgende secties wordt ervan uitgegaan dat query's geen expliciete bereikvariabeltypen hebben.

voorbeeld: het voorbeeld

from Customer c in customers
where c.City == "London"
select c

wordt omgezet in

from c in (customers).Cast<Customer>()
where c.City == "London"
select c

de definitieve vertaling daarvan is

customers.
Cast<Customer>().
Where(c => c.City == "London")

einde voorbeeld

Opmerking: expliciete bereikvariabeletypen zijn handig voor het uitvoeren van query's op verzamelingen die de niet-algemene IEnumerable-interface implementeren, maar niet de algemene IEnumerable<T> interface. In het bovenstaande voorbeeld zou dit het geval zijn als klanten van het type ArrayListzijn. eindnotitie

12.20.3.4 Degeneratieve zoekuitdrukkingen

Een query-expressie van het formulier

from «x» in «e» select «x»

wordt omgezet in

( «e» ) . Select ( «x» => «x» )

voorbeeld: het voorbeeld

from c in customers
select c

wordt omgezet in

(customers).Select(c => c)

einde voorbeeld

Een degenereerde query-expressie is een expressie die de elementen van de bron triviaal selecteert.

Opmerking: latere fasen van de vertaling (§12.20.3.6 en §12.20.3.7) verwijderen degenererende query's die zijn geïntroduceerd door andere vertaalstappen door ze te vervangen door hun bron. Het is echter belangrijk om ervoor te zorgen dat het resultaat van een query-expressie nooit het bronobject zelf is. Anders kan het retourneren van het resultaat van een dergelijke query per ongeluk persoonlijke gegevens (bijvoorbeeld een elementmatrix) blootstellen aan een aanroeper. Daarom beveiligt deze stap degenereerde query's die rechtstreeks in de broncode zijn geschreven door expliciet Select aan te roepen op de bron. Het is vervolgens aan de implementeerfuncties van Select en andere queryoperators om ervoor te zorgen dat deze methoden nooit het bronobject zelf retourneren. eindnotitie

12.20.3.5 Vanuit, let, waar, voeg toe en sorteer op clausules

Een query-expressie met een tweede from-component gevolgd door een select-component

from «x1» in «e1»  
from «x2» in «e2»  
select «v»

wordt omgezet in

( «e1» ) . SelectMany( «x1» => «e2» , ( «x1» , «x2» ) => «v» )

voorbeeld: het voorbeeld

from c in customers
from o in c.Orders
select new { c.Name, o.OrderID, o.Total }

wordt omgezet in

(customers).
SelectMany(c => c.Orders,
(c,o) => new { c.Name, o.OrderID, o.Total }
)

einde voorbeeld

Een query-expressie met een tweede from component gevolgd door een querytekst Q met een niet-lege set querytekstcomponenten:

from «x1» in «e1»
from «x2» in «e2»
Q

wordt omgezet in

from * in («e1») . SelectMany( «x1» => «e2» ,
                              ( «x1» , «x2» ) => new { «x1» , «x2» } )
Q

voorbeeld: het voorbeeld

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

wordt omgezet in

from * in (customers).
   SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

de definitieve vertaling daarvan is

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

waarbij x een door compiler gegenereerde id is die anders onzichtbaar en niet toegankelijk is.

einde voorbeeld

Een let-uitdrukking samen met de voorgaande from-clausule:

from «x» in «e»  
let «y» = «f»  
...

wordt omgezet in

from * in ( «e» ) . Select ( «x» => new { «x» , «y» = «f» } )  
...

voorbeeld: het voorbeeld

from o in orders
let t = o.Details.Sum(d => d.UnitPrice * d.Quantity)
where t >= 1000
select new { o.OrderID, Total = t }

wordt omgezet in

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 }

de definitieve vertaling daarvan is

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

waarbij x een door compiler gegenereerde id is die anders onzichtbaar en niet toegankelijk is.

einde voorbeeld

Een where-uitdrukking samen met de voorgaande from-clausule:

from «x» in «e»  
where «f»  
...

wordt omgezet in

from «x» in ( «e» ) . Where ( «x» => «f» )  
...

Een join-component onmiddellijk gevolgd door een select-component

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
select «v»

wordt omgezet in

( «e1» ) . Join( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «x2» ) => «v» )

voorbeeld: het voorbeeld

from c in customers
join o in orders on c.CustomerID equals o.CustomerID
select new { c.Name, o.OrderDate, o.Total }

wordt omgezet in

(customers).Join(
   orders,
   c => c.CustomerID, o => o.CustomerID,
   (c, o) => new { c.Name, o.OrderDate, o.Total })

einde voorbeeld

Een join-component gevolgd door een querybody-component:

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
...

wordt omgezet in

from * in ( «e1» ) . Join(  
«e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «x2» ) => new { «x1» , «x2» })  
...

Een join-into-component onmiddellijk gevolgd door een select-component

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into «g»  
select «v»

wordt omgezet in

( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» ,
                     ( «x1» , «g» ) => «v» )

Een join into-component gevolgd door een querybody-component

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into *g»  
...

wordt omgezet in

from * in ( «e1» ) . GroupJoin(  
   «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «g» ) => new { «x1» , «g» })
...

voorbeeld: het voorbeeld

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 }

wordt omgezet in

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 }

de definitieve vertaling daarvan is

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

waarbij x en y door compiler gegenereerde id's zijn die anders onzichtbaar en niet toegankelijk zijn.

einde voorbeeld

Een orderby-clausule en de voorgaande from-clausule:

from «x» in «e»  
orderby «k1» , «k2» , ... , «kn»  
...

wordt omgezet in

from «x» in ( «e» ) .
OrderBy ( «x» => «k1» ) .
ThenBy ( «x» => «k2» ) .
... .
ThenBy ( «x» => «kn» )
...

Als een ordering-component een indicator voor aflopende richting opgeeft, wordt in plaats daarvan een oproep van OrderByDescending of ThenByDescending uitgevoerd.

voorbeeld: het voorbeeld

from o in orders
orderby o.Customer.Name, o.Total descending
select o

heeft de definitieve vertaling

(orders)
    .OrderBy(o => o.Customer.Name)
    .ThenByDescending(o => o.Total)

einde voorbeeld

In de volgende vertalingen wordt ervan uitgegaan dat er geen let, where, join- of orderby-componenten zijn, en niet meer dan de eerste from component in elke query-expressie.

12.20.3.6 Clausules selecteren

Een query-expressie van het formulier

from «x» in «e» select «v»

wordt omgezet in

( «e» ) . Select ( «x» => «v» )

behalve wanneer «v» de identifier «x»is, is de vertaling gewoon

( «e» )

voorbeeld: het voorbeeld

from c in customers.Where(c => c.City == "London")
select c

wordt simpelweg vertaald in

(customers).Where(c => c.City == "London")

einde voorbeeld

12.20.3.7 Groepsclausules

Een group-clausule

from «x» in «e» group «v» by «k»

wordt omgezet in

( «e» ) . GroupBy ( «x» => «k» , «x» => «v» )

behalve wanneer «v» de id «x»is, is de vertaling

( «e» ) . GroupBy ( «x» => «k» )

voorbeeld: het voorbeeld

from c in customers
group c.Name by c.Country

wordt omgezet in

(customers).GroupBy(c => c.Country, c => c.Name)

einde voorbeeld

12.20.3.8 Transparante id's

Bepaalde vertalingen injecteren bereikvariabelen met transparante id's aangeduid door *. Transparante id's bestaan alleen als een tussenliggende stap in het vertaalproces voor query-expressies.

Wanneer een queryvertaling een transparante id injecteert, worden verdere vertaalstappen de transparante id doorgestuurd naar anonieme functies en anonieme object-initialisaties. In deze contexten hebben transparante id's het volgende gedrag:

  • Wanneer een transparante id als een parameter in een anonieme functie voorkomt, zijn de leden van het bijbehorende anonieme type automatisch beschikbaar binnen het lichaam van de anonieme functie.
  • Wanneer een lid met een zichtbare identificatie binnen het bereik valt, bevinden de leden van dat lid zich ook binnen het bereik.
  • Wanneer een transparante id voorkomt als liddeclaratie in een anonieme object-initialisatiefunctie, introduceert deze een lid met een transparante id.

In de hierboven beschreven vertaalstappen worden transparante id's altijd samen met anonieme typen geïntroduceerd, met de bedoeling om meerdere bereikvariabelen vast te leggen als leden van één object. Een implementatie van C# is toegestaan om een ander mechanisme te gebruiken dan anonieme typen om meerdere bereikvariabelen te groeperen. In de volgende vertaalvoorbeelden wordt ervan uitgegaan dat anonieme typen worden gebruikt en dat er één mogelijke vertaling van transparante id's wordt weergegeven.

voorbeeld: het voorbeeld

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.Total }

wordt omgezet in

from * in (customers).SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.Total }

die verder wordt vertaald in

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(* => o.Total)
    .Select(\* => new { c.Name, o.Total })

die, wanneer transparante identificatoren worden gewist, gelijk is aan

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(x => x.o.Total)
    .Select(x => new { x.c.Name, x.o.Total })

waarbij x een door compiler gegenereerde id is die anders onzichtbaar en niet toegankelijk is.

Het voorbeeld

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 }

wordt omgezet in

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 }

die verder wordt gereduceerd tot

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

de definitieve vertaling daarvan is

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

waarbij x en y door compiler gegenereerde id's zijn die anders onzichtbaar en niet toegankelijk zijn. einde voorbeeld

12.20.4 Het queryexpressiepatroon

Met het queryexpressiepatroon wordt een patroon vastgesteld van methoden die typen kunnen implementeren ter ondersteuning van query-expressies.

Een algemeen type C<T> ondersteunt het query-expressiepatroon als de methoden voor openbare leden en de openbaar toegankelijke extensiemethoden kunnen worden vervangen door de volgende klassedefinitie. De leden en toegankelijke uitbreidingsmethoden worden aangeduid als de 'structuur' van een algemeen type C<T>. Een algemeen type wordt gebruikt om de juiste relaties tussen parameter- en retourtypen te illustreren, maar het is ook mogelijk om het patroon voor niet-algemene typen te implementeren.

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

De bovenstaande methoden gebruiken de generieke gedelegeerde typen Func<T1, R> en Func<T1, T2, R>, maar ze hadden net zo goed andere gedelegeerde typen of expressie-boomtypen met dezelfde relaties in parameter- en retourtypen kunnen gebruiken.

Opmerking: de aanbevolen relatie tussen C<T> en O<T> die ervoor zorgt dat de methoden ThenBy en ThenByDescending alleen beschikbaar zijn op het resultaat van een OrderBy of OrderByDescending. eindnotitie

Opmerking: de aanbevolen vorm van het resultaat van GroupBy: een reeks reeksen, waarbij elke binnenste reeks een extra Key eigenschap heeft. eindnotitie

Opmerking: Omdat query-expressies middels een syntactische toewijzing worden vertaald naar methodeaanroepen, hebben typen aanzienlijke flexibiliteit in hoe zij enig of alle onderdelen van het query-expressiepatroon implementeren. De methoden van het patroon kunnen bijvoorbeeld worden geïmplementeerd als exemplaarmethoden of als uitbreidingsmethoden omdat de twee dezelfde aanroepsyntaxis hebben en de methoden gemachtigden of expressiestructuren kunnen aanvragen omdat anonieme functies kunnen worden geconverteerd naar beide. Typen die slechts een deel van het queryexpressiepatroon implementeren, ondersteunen alleen vertalingen van query-expressies die worden toegewezen aan de methoden die door het type worden ondersteund. eindnotitie

Opmerking: de System.Linq-naamruimte biedt een implementatie van het queryexpressiepatroon voor elk type dat de System.Collections.Generic.IEnumerable<T>-interface implementeert. eindnotitie

12.21 Toewijzingsoperator

12.21.1 Algemeen

Alle toewijzingsoperatoren wijzen een nieuwe waarde toe aan een variabele, een eigenschap, een gebeurtenis of een indexeerelement. De uitzondering, = ref, wijst een variabele verwijzing (§9,5) toe aan een verwijzingsvariabele (§9,7).

assignment
    : unary_expression assignment_operator expression
    ;

assignment_operator
    : '=' 'ref'? | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<='
    | right_shift_assignment
    ;

De linkeroperand van een toewijzing moet een expressie zijn die is geclassificeerd als een variabele, of, met uitzondering van = ref, een eigenschapstoegang, een indexeerfunctietoegang, een gebeurtenistoegang of een tuple. Een declaratie-expressie is niet rechtstreeks toegestaan als een linkeroperand, maar kan optreden als een stap in de evaluatie van een deconstructing-toewijzing.

De operator = wordt de eenvoudige toewijzingsoperatorgenoemd. Hiermee wordt de waarde of waarden van de rechteroperand toegewezen aan de variabele, eigenschap, indexeerelement of tuple-elementen die door de linkeroperand worden gegeven. De linkeroperand van de eenvoudige toewijzingsoperator mag geen toegang tot gebeurtenissen zijn (behalve zoals beschreven in §15.8.2). De eenvoudige toewijzingsoperator wordt beschreven in §12.21.2.

De operator = ref wordt de verwijzingstoewijzing genoemd. Het zorgt ervoor dat de rechteroperand, die een variable_reference (§9.5) is, het referent van de verwijzingsvariabele wordt die door de linkeroperand wordt aangewezen. De "ref"-toewijzingsoperator wordt beschreven in §12.21.3.

De andere toewijzingsoperatoren dan de =- en = ref-operators worden de operatoren voor samengestelde toewijzingen genoemd. Deze operators voeren de aangegeven bewerking uit op de twee operanden en wijzen vervolgens de resulterende waarde toe aan het element variabele, eigenschap of indexeerfunctie dat is opgegeven door de linkeroperand. De samengestelde toewijzingsoperatoren worden beschreven in §12.21.4.

De operators += en -= met een expressie voor gebeurtenistoegang als de linkeroperand worden de operatoren gebeurtenistoewijzinggenoemd. Er is geen andere toewijzingsoperator geldig met gebeurtenistoegang als de linkeroperand. De gebeurtenistoewijzingsoperatoren worden beschreven in §12.21.5.

De toewijzingsoperatoren zijn rechts-associatief, wat betekent dat bewerkingen van rechts naar links worden gegroepeerd.

Voorbeeld: een expressie van het formulier a = b = c wordt geëvalueerd als a = (b = c). einde voorbeeld

12.21.2 Eenvoudige opdracht

De operator = wordt de eenvoudige toewijzingsoperator genoemd.

Als de linkeroperand van een eenvoudige toewijzing van de vorm E.P of E[Ei] is, waarbij E het compilatietijdtype dynamicheeft, is de toewijzing dynamisch gebonden (§12.3.3). In dit geval is het compile-tijdtype van de toewijzingsexpressie dynamic, en de hieronder beschreven oplossing zal plaatsvinden op run-time op basis van het run-time type van E. Als de linkeroperand in de vorm van E[Ei] is waarbij ten minste één element van Ei het type tijdens de compilatie heeft dynamicen het type tijdens de compilatie van E geen array is, is de resulterende indextoegang dynamisch gebonden, maar met beperkte controle tijdens de compilatie (§12.6.5).

Een eenvoudige toewijzing waarbij de linkeroperand wordt geclassificeerd als een tuple, wordt ook wel een deconstructietoewijzinggenoemd. Als een van de tuple-elementen van de linkeroperand een elementnaam heeft, treedt er een compilatiefout op. Als een van de tuple-elementen van de linkeroperand een declaration_expression is en een ander element geen declaration_expression of een eenvoudige verwijdering is, treedt er een compilatietijdfout op.

Het type eenvoudige toewijzing x = y is het type toewijzing aan x van y, dat recursief wordt bepaald als volgt:

  • Als x een tuple-expressie (x1, ..., xn)is en y kan worden gedeconstrueerd tot een tuple-expressie (y1, ..., yn) met n elementen (§12.7), en elke toewijzing aan xi van yi het type Tiheeft, dan heeft de toewijzing het type (T1, ..., Tn).
  • Anders, als x als variabele wordt geclassificeerd, de variabele niet readonlyis, x een type Theeft en y een impliciete conversie naar Theeft, dan heeft de toewijzing het type T.
  • Als x is geclassificeerd als een impliciet getypte variabele (bijvoorbeeld een impliciet getypeerde declaratie-expressie) en y een type Theeft, wordt het afgeleid type van de variabele Ten heeft de toewijzing het type T.
  • Als x is geclassificeerd als een eigenschap of indexeerfunctie, en deze een toegankelijke setmutator heeft, x een type Theeft, en y een impliciete conversie naar Theeft, dan heeft de toewijzing het type T.
  • Anders is de toewijzing niet geldig en treedt er een bindingstijdfout op.

De runtimeverwerking van een eenvoudige toewijzing van het formulier x = y met het type T wordt uitgevoerd als een toewijzing aan x van y met het type T, dat bestaat uit de volgende recursieve stappen:

  • x wordt geëvalueerd als het nog niet eerder is geëvalueerd.
  • Als x als variabele wordt geclassificeerd, wordt y geëvalueerd en indien nodig geconverteerd naar T via een impliciete conversie (§10.2).
    • Als de variabele van x een matrixelement van een reference_typeis, wordt er een runtimecontrole uitgevoerd om ervoor te zorgen dat de waarde die wordt berekend voor y compatibel is met het matrixexemplaren waarvan x een element is. De controle slaagt als y is nullof als er een impliciete verwijzingsconversie (§10.2.8) bestaat van het type exemplaar waarnaar wordt verwezen door y naar het werkelijke elementtype van het matrixexemplaren met x. Anders wordt er een System.ArrayTypeMismatchException gegooid.
    • De waarde die het resultaat is van de evaluatie en conversie van y wordt opgeslagen op de locatie die is opgegeven door de evaluatie van xen wordt als resultaat van de toewijzing opgeleverd.
  • Als x is geclassificeerd als een eigenschap of indexertoegang:
    • y wordt geëvalueerd en, indien nodig, geconverteerd naar T via een impliciete conversie (§10,2).
    • De set accessor van x wordt aangeroepen met de waarde die het resultaat is van de evaluatie en conversie van y als waardeargument.
    • De waarde die voortkomt uit de evaluatie en conversie van y wordt als het resultaat van de toewijzing geleverd.
  • Als x is geclassificeerd als een tuple (x1, ..., xn) met ariteit n:
    • y wordt gedeconstrueerd met n elementen tot een tuple-expressie e.
    • een resultaat-tuple-t wordt gemaakt door e te converteren naar T met behulp van een impliciete tupleconversie.
    • voor elke xi in volgorde van links naar rechts wordt een toewijzing aan xi van t.Itemi uitgevoerd, behalve dat de xi niet opnieuw worden geëvalueerd.
    • t wordt verkregen als gevolg van de toewijzing.

Opmerking: als het type compilatietijd van x is dynamic en er een impliciete conversie is van het type compilatietijd van y naar dynamic, is er geen runtime-oplossing vereist. eindnotitie

Opmerking: De regels voor covariantie van matrix (§17,6) staan toe dat een waarde van een matrixtype A[] een verwijzing naar een exemplaar van een matrixtype B[]is, mits er een impliciete verwijzingsconversie bestaat van B tot A. Vanwege deze regels is voor toewijzing aan een matrixelement van een reference_type een runtimecontrole vereist om ervoor te zorgen dat de waarde die wordt toegewezen compatibel is met het matrixexemplaren. In het voorbeeld

string[] sa = new string[10];
object[] oa = sa;
oa[0] = null;              // OK
oa[1] = "Hello";           // OK
oa[2] = new ArrayList();   // ArrayTypeMismatchException

de laatste toewijzing zorgt ervoor dat een System.ArrayTypeMismatchException wordt geworpen omdat een verwijzing naar een ArrayList niet kan worden opgeslagen in een element van een string[].

eindnotitie

Wanneer een eigenschap of indexeerfunctie die is gedeclareerd in een struct_type het doel is van een toewijzing, wordt de exemplaarexpressie die is gekoppeld aan de toegang tot de eigenschap of indexeerfunctie geclassificeerd als een variabele. Als de exemplaarexpressie wordt geclassificeerd als een waarde, treedt er een bindingstijdfout op.

Opmerking: Vanwege §12.8.7geldt dezelfde regel ook voor velden. eindnotitie

Voorbeeld: Gegeven de declaraties:

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

in het voorbeeld

Point p = new Point();
p.X = 100;
p.Y = 100;
Rectangle r = new Rectangle();
r.A = new Point(10, 10);
r.B = p;

de toewijzingen aan p.X, p.Y, r.Aen r.B zijn toegestaan omdat p en r variabelen zijn. Echter, in het voorbeeld

Rectangle r = new Rectangle();
r.A.X = 10;
r.A.Y = 10;
r.B.X = 100;
r.B.Y = 100;

de toewijzingen zijn allemaal ongeldig, omdat r.A en r.B geen variabelen zijn.

einde voorbeeld

12.21.3 Ref-toewijzing

De = ref operator staat bekend als de toewijzingsoperator .

De linkeroperand moet een expressie zijn waarmee een verwijzingsvariabele (§9,7), een verwijzingsparameter (anders dan this), een uitvoerparameter of een invoerparameter wordt gekoppeld. De rechteroperand moet een uitdrukking zijn die resulteert in een variable_reference die een waarde aanwijst van hetzelfde type als de linkeroperand.

Het is een compilatietijdfout als de ref-safe-context (§9.7.2) van de linkeroperand breder is dan de ref-safe-context van de rechteroperand.

De juiste operand wordt zeker toegewezen op het punt van de verw-toewijzing.

Wanneer de linkeroperand wordt gebonden aan een uitvoerparameter, is het een fout als deze uitvoerparameter niet definitief is toegewezen aan het begin van de ref-toewijzingsoperator.

Als de linkeroperand een beschrijfbare ref is (d.w.w.v. een andere aanduiding dan een ref readonly lokale of invoerparameter), is de rechteroperand een schrijfbare variable_reference. Als de variabele van de rechteroperand beschrijfbaar is, kan de linkeroperand een beschrijfbare of alleen-lezen referentie zijn.

De bewerking maakt de linkeroperand een alias van de rechteroperandvariabele. De alias kan alleen-lezen worden gemaakt, zelfs als de juiste operandvariabele schrijfbaar is.

De ref-toewijzingsoperator levert een variable_reference van het toegewezen type op. Deze kan worden geschreven als de linkeroperand schrijfbaar is.

De operator voor het toewijzen van verwijzingen leest de opslaglocatie waar de rechteroperand naar verwijst niet.

voorbeeld: hier volgen enkele voorbeelden van het gebruik van = 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
}

einde voorbeeld

Opmerking: wanneer u code leest met behulp van een operator voor = ref, kan het verleidelijk zijn om het ref deel te lezen als onderdeel van de operand. Dit is met name verwarrend wanneer de operand een voorwaardelijke ?: expressie is. Als u bijvoorbeeld ref int a = ref b ? ref x : ref y; leest, is het belangrijk om dit te lezen als = ref de operator en b ? ref x : ref y de juiste operand zijn: ref int a = ref (b ? ref x : ref y);. Belangrijk is dat de expressie ref bniet deel uitmaakt van die verklaring, ook al lijkt dat op het eerste gezicht zo. eindnotitie

12.21.4 Samengestelde toewijzing

Als de linkeroperand van een samengestelde toewijzing de vorm heeft E.P of E[Ei] waarbij E het type compilatietijd heeft dynamic, is de toewijzing dynamisch gebonden (§12.3.3). In dit geval is het compile-tijdtype van de toewijzingsexpressie dynamic, en de hieronder beschreven oplossing zal plaatsvinden op run-time op basis van het run-time type van E. Als de linkeroperand in de vorm van E[Ei] is waarbij ten minste één element van Ei het type tijdens de compilatie heeft dynamicen het type tijdens de compilatie van E geen array is, is de resulterende indextoegang dynamisch gebonden, maar met beperkte controle tijdens de compilatie (§12.6.5).

Een werking van het formulier x «op»= y wordt verwerkt door een overbelastingsresolutie van binaire operatoren toe te passen (§12.4.5) alsof de bewerking is geschreven x «op» y. Dan

  • Als het retourtype van de geselecteerde operator impliciet wordt omgezet in het type x, wordt de bewerking geëvalueerd als x = x «op» y, behalve dat x slechts eenmaal wordt geëvalueerd.
  • Als de geselecteerde operator anders een vooraf gedefinieerde operator is, als het retourtype van de geselecteerde operator expliciet wordt omgezet in het type x en als y impliciet wordt omgezet in het type x of als de operator een shift-operator is, wordt de bewerking geëvalueerd als x = (T)(x «op» y), waarbij T het type xis, behalve dat x slechts één keer wordt geëvalueerd.
  • Anders is de samengestelde toewijzing ongeldig en treedt er een bindingstijdfout op.

De term 'slechts eenmaal geëvalueerd' betekent dat in de evaluatie van x «op» yde resultaten van alle samenstellende expressies van x tijdelijk worden opgeslagen en vervolgens opnieuw worden gebruikt bij het uitvoeren van de toewijzing aan x.

Voorbeeld: in de toewijzings-A()[B()] += C(), waarbij A een methode is die int[]retourneert en B en C methoden zijn die intretourneren, worden de methoden slechts eenmaal aangeroepen, in de volgorde A, B, C. einde voorbeeld

Wanneer de linkeroperand van een samengestelde toewijzing een eigenschapstoegang of indexeerfunctietoegang is, moet de eigenschap of indexeerfunctie zowel een get-accessor als een set accessor hebben. Als dit niet het geval is, treedt er een bindingstijdfout op.

Met de tweede regel hierboven kan x «op»= y worden geëvalueerd als x = (T)(x «op» y) in bepaalde contexten. De regel bestaat zodanig dat de vooraf gedefinieerde operators kunnen worden gebruikt als samengestelde operatoren wanneer de linkeroperand van het type sbyte, byte, short, ushortof charis. Zelfs wanneer beide argumenten van een van deze typen zijn, produceren de vooraf gedefinieerde operatoren een resultaat van het type int, zoals beschreven in §12.4.7.3. Zonder cast zou het dus niet mogelijk zijn om het resultaat toe te wijzen aan de linkeroperand.

Het intuïtieve effect van de regel voor vooraf gedefinieerde operators is simpelweg dat x «op»= y is toegestaan als zowel van x «op» y als x = y zijn toegestaan.

Voorbeeld: In de volgende code

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

de intuïtieve reden voor elke fout is dat een bijbehorende eenvoudige toewijzing ook een fout zou zijn geweest.

einde voorbeeld

Opmerking: dit betekent ook dat samengestelde toewijzingsbewerkingen lifted operators ondersteunen. Aangezien een samengestelde toewijzing x «op»= y wordt geëvalueerd als x = x «op» y of x = (T)(x «op» y), omvatten de evaluatieregels impliciet verheven operatoren. eindnotitie

12.21.5 Gebeurtenistoewijzing

Als de linkeroperand van a += or -= operator is geclassificeerd als een gebeurtenistoegang, wordt de expressie als volgt geëvalueerd:

  • De eventuele exemplaarexpressie van de toegang tot een gebeurtenis wordt geëvalueerd.
  • De rechteroperand van de operator += of -= wordt geëvalueerd en indien nodig geconverteerd naar het type linkeroperand via een impliciete conversie (§10,2).
  • Er wordt een gebeurtenistoegangspunt van de gebeurtenis aangeroepen, met een lijst met argumenten die bestaat uit de waarde die in de vorige stap is berekend. Als de operator +=is, wordt de toevoegaccessor aangeroepen; als de operator -=is, wordt de verwijderaccessor aangeroepen.

Een expressie voor gebeurtenistoewijzing levert geen waarde op. Een expressie voor gebeurtenistoewijzing is dus alleen geldig in de context van een statement_expression (§13.7).

12.22 Expressie

Een expressie is een non_assignment_expression of een toewijzing.

expression
    : non_assignment_expression
    | assignment
    ;

non_assignment_expression
    : declaration_expression
    | conditional_expression
    | lambda_expression
    | query_expression
    ;

12.23 Constante uitdrukkingen

Een constante expressie is een expressie die tijdens het compileren volledig wordt geëvalueerd.

constant_expression
    : expression
    ;

Een constante expressie heeft de waarde null of een van de volgende typen:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string;
  • een opsommingstype; of
  • een standaardwaardeexpressie (§12.8.21) voor een referentietype.

Alleen de volgende constructies zijn toegestaan in constante expressies:

  • Letterlijke gegevens (inclusief de letterlijke null).
  • Verwijzingen naar const-leden van klasse- en structuurtypen.
  • Verwijzingen naar leden van opsommingstypen.
  • Verwijzingen naar lokale constanten.
  • Subexpressies tussen haakjes, die zelf constante expressies zijn.
  • Cast-expressies.
  • checked en unchecked expressies.
  • nameof expressies.
  • De vooraf gedefinieerde +, -, ! (logische negatie) en ~ unaire operatoren.
  • De vooraf gedefinieerde +, -, *, /, %, <<, >>, &, |, ^, &&, ||, ==, !=, <, >, <=en >= binaire operatoren.
  • De ?: voorwaardelijke operator.
  • De null-forgiving operator ! (§12.8.9).
  • sizeof expressies, mits het niet-beheerde type een van de typen is die zijn opgegeven in §23.6.9 waarvoor sizeof een constante waarde retourneert.
  • Standaardwaarde-expressies, mits het type een van de bovenstaande typen is.

De volgende conversies zijn toegestaan in constante expressies:

  • Identiteitsconversies
  • Numerieke conversies
  • Enumeratieconversies
  • Omzettingen van constante expressies
  • Impliciete en expliciete verwijzingsconversies, mits de bron van de conversies een constante expressie is die resulteert in de null waarde.

Opmerking: andere conversies, waaronder boksen, uitpakken en impliciete verwijzingsconversies van niet-null waarden, zijn niet toegestaan in constante expressies. eindnotitie

Voorbeeld: In de volgende code

class C
{
    const object i = 5;         // error: boxing conversion not permitted
    const object str = "hello"; // error: implicit reference conversion
}

de initialisatie van i is een fout omdat een boksconversie is vereist. De initialisatie van str is een fout omdat een impliciete verwijzingsconversie van een niet-null waarde is vereist.

einde voorbeeld

Wanneer een expressie voldoet aan de bovenstaande vereisten, wordt de expressie geëvalueerd tijdens het compileren. Dit geldt zelfs als de expressie een subexpressie is van een grotere expressie die niet-constante constructies bevat.

De evaluatie van de compilatietijd van constante expressies maakt gebruik van dezelfde regels als de runtime-evaluatie van niet-constante expressies, behalve dat wanneer de runtime-evaluatie een uitzondering zou hebben veroorzaakt, zorgt de compileertijd-evaluatie ervoor dat er een compilatiefout optreedt.

Tenzij een constante expressie expliciet in een unchecked context wordt geplaatst, veroorzaakt overloop die optreden in rekenkundige bewerkingen en conversies van integraal type tijdens de evaluatie van de compilatietijd van de expressie altijd compilatiefouten (§12.8.20).

Constante expressies zijn vereist in de onderstaande contexten en dit wordt aangegeven in de grammatica met behulp van constant_expression. In deze contexten treedt een compilatietijdfout op als een expressie niet volledig kan worden geëvalueerd tijdens het compileren.

  • Constante definities (§15.4)
  • Opsommingsliddeclaraties (§19.4)
  • Standaardargumenten van parameterlijsten (§15.6.2)
  • case labels van een switch verklaring (§13.8.3).
  • goto case verklaringen (§13.10.4)
  • Dimensielengten in een expressie voor het maken van een matrix (§12.8.17.5) met een initialisatiefunctie.
  • Kenmerken (§22)
  • In een constant_pattern (§11.2.3)

Met een impliciete expressieconversie (§10.2.11) kan een constante expressie van het type int worden geconverteerd naar sbyte, byte, short, ushort, uintof ulong, mits de waarde van de constante expressie binnen het bereik van het doeltype valt.

12.24 Booleaanse expressies

Een boolean_expression is een expressie die een resultaat oplevert van het type bool; hetzij rechtstreeks of via de toepassing van operator true in bepaalde contexten, zoals opgegeven in de volgende:

boolean_expression
    : expression
    ;

De voorwaardelijke expressie van een if_statement (§13.8.2), while_statement (§13.9.2), do_statement (§13.9.3), of for_statement (§13.9.4) bestaat uit een boolean_expression. De besturingsvoorwaardelijke expressie van de operator ?: (§12.18) volgt dezelfde regels als een boolean_expression, maar om redenen van prioriteit van de operator wordt als een null_coalescing_expressiongeclassificeerd.

Een boolean_expressionE is vereist om als volgt een waarde van het type boolte kunnen produceren:

  • Als E impliciet converteerbaar is naar bool, wordt tijdens runtime de impliciete conversie toegepast.
  • Anders wordt een unaire overbelastingsresolutie (§12.4.4) gebruikt om een unieke beste implementatie van operator true op Ete vinden en die implementatie wordt toegepast tijdens runtime.
  • Als er geen dergelijke operator wordt gevonden, treedt er een bindingstijdfout op.