Door de gebruiker gedefinieerde operators gecontroleerd
Notitie
Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.
Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Deze verschillen worden vastgelegd in de relevante LDM-notities (Language Design Meeting).
Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.
Samenvatting
C# moet ondersteuning bieden voor het definiëren van checked
varianten van de volgende door de gebruiker gedefinieerde operators, zodat gebruikers zich zo nodig kunnen afmelden voor overloopgedrag:
- De
++
en--
unaire operatoren §12.8.16 en §12.9.6. - De
-
unaire operator §12.9.3. - De binaire operatoren
+
,-
,*
en/
§12.10. - Expliciete conversieoperators.
Motivatie
Een gebruiker kan geen type declareren en zowel gecontroleerde als niet-gecontroleerde versies van een operator ondersteunen. Hierdoor is het lastig om verschillende algoritmen te overzetten om de voorgestelde generic math
interfaces te gebruiken die door het bibliothekenteam worden weergegeven. Dit maakt het ook onmogelijk om een type zoals Int128
of UInt128
beschikbaar te maken zonder dat de taal tegelijkertijd een eigen ondersteuning verzendt om te voorkomen dat wijzigingen fouten veroorzaken.
Gedetailleerd ontwerp
Syntaxis
Grammatica bij operators (§15.10) wordt aangepast om checked
trefwoord toe te staan na het operator
trefwoord vlak voor het operatortoken:
overloadable_unary_operator
: '+' | 'checked'? '-' | '!' | '~' | 'checked'? '++' | 'checked'? '--' | 'true' | 'false'
;
overloadable_binary_operator
: 'checked'? '+' | 'checked'? '-' | 'checked'? '*' | 'checked'? '/' | '%' | '&' | '|' | '^' | '<<'
| right_shift | '==' | '!=' | '>' | '<' | '>=' | '<='
;
conversion_operator_declarator
: 'implicit' 'operator' type '(' type identifier ')'
| 'explicit' 'operator' 'checked'? type '(' type identifier ')'
;
Bijvoorbeeld:
public static T operator checked ++(T x) {...}
public static T operator checked --(T x) {...}
public static T operator checked -(T x) {...}
public static T operator checked +(T lhs, T rhs) {...}
public static T operator checked -(T lhs, T rhs) {...}
public static T operator checked *(T lhs, T rhs) {...}
public static T operator checked /(T lhs, T rhs) {...}
public static explicit operator checked U(T x) {...}
public static T I1.operator checked ++(T x) {...}
public static T I1.operator checked --(T x) {...}
public static T I1.operator checked -(T x) {...}
public static T I1.operator checked +(T lhs, T rhs) {...}
public static T I1.operator checked -(T lhs, T rhs) {...}
public static T I1.operator checked *(T lhs, T rhs) {...}
public static T I1.operator checked /(T lhs, T rhs) {...}
public static explicit I1.operator checked U(T x) {...}
Hieronder wordt een operator met het trefwoord checked
aangeduid als een checked operator
en een operator zonder als een regular operator
. Deze voorwaarden zijn niet van toepassing op operators die geen checked
formulier hebben.
Semantiek
Er wordt verwacht dat een door de gebruiker gedefinieerde checked operator
een uitzondering genereert wanneer het resultaat van een bewerking te groot is om in het doeltype weer te geven. Wat betekent dat het te groot is, is afhankelijk van de aard van het doeltype en wordt niet voorgeschreven door de taal. De uitzondering die wordt gegenereerd, is meestal een System.OverflowException
, maar de taal heeft hier geen specifieke vereisten voor.
Een door de gebruiker gedefinieerde regular operator
naar verwachting geen uitzondering genereert wanneer het resultaat van een bewerking te groot is om in het doeltype weer te geven. In plaats daarvan wordt verwacht dat er een exemplaar wordt geretourneerd dat een afgekapt resultaat vertegenwoordigt. Wat betekent dat het te groot is en afgekapt moet worden, hangt af van de aard van het doeltype en wordt niet voorgeschreven door de taal.
Alle bestaande door de gebruiker gedefinieerde operators met checked
formulier die worden ondersteund, vallen onder de categorie van regular operators
. Het is duidelijk dat velen van hen waarschijnlijk niet de hierboven opgegeven semantiek volgen, maar voor het doel van semantische analyse wordt ervan uitgegaan dat de compiler aanneemt dat ze dat wel doen.
Ingeschakeld versus uitgeschakeld context binnen een checked operator
Ingeschakelde/uitgeschakelde context in de hoofdtekst van een checked operator
wordt niet beïnvloed door de aanwezigheid van het checked
trefwoord. Met andere woorden, de context is gelijk aan onmiddellijk aan het begin van de operatordeclaratie. De ontwikkelaar moet expliciet overschakelen van de context als een deel van het algoritme niet afhankelijk is van de standaardcontext.
Namen in metagegevens
Sectie "I.10.3.1 Unary operators" van ECMA-335 wordt aangepast om op_CheckedIncrement, op_CheckedDecrement, op_CheckedUnaryNegation op te nemen als de namen voor methoden die gecontroleerde ++
, --
en -
unaire operatoren implementeren.
Sectie "I.10.3.2 Binaire operatoren" van ECMA-335 wordt aangepast om op_CheckedAddition, op_CheckedSubtraction, op_CheckedMultiply, en op_CheckedDivision als de namen te gebruiken voor methoden die gecontroleerde +
, -
, *
en /
binaire operatoren implementeren.
Sectie "I.10.3.3 Conversieoperators" van ECMA-335 wordt aangepast om op_CheckedExplicit op te nemen als de naam voor een methode die een gecontroleerde expliciete conversieoperator implementeert.
Unaire operators
Unary checked operators
volgt de regels uit §15.10.2.
Daarnaast vereist een checked operator
declaratie een paarsgewijze verklaring van een regular operator
(het retourtype moet ook overeenkomen). Anders treedt er een compilatiefout op.
public struct Int128
{
// This is fine, both a checked and regular operator are defined
public static Int128 operator checked -(Int128 lhs);
public static Int128 operator -(Int128 lhs);
// This is fine, only a regular operator is defined
public static Int128 operator --(Int128 lhs);
// This should error, a regular operator must also be defined
public static Int128 operator checked ++(Int128 lhs);
}
Binaire operatoren
Binaire checked operators
volgen de regels uit §15.10.3.
Daarnaast vereist een checked operator
declaratie een paarsgewijze verklaring van een regular operator
(het retourtype moet ook overeenkomen). Anders treedt er een compilatiefout op.
public struct Int128
{
// This is fine, both a checked and regular operator are defined
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
// This is fine, only a regular operator is defined
public static Int128 operator -(Int128 lhs, Int128 rhs);
// This should error, a regular operator must also be defined
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
}
Door de gebruiker gedefinieerde kandidaatoperators
De sectie voor kandidaat-gebruikersactiviteiten (§12.4.6) zal als volgt worden aangepast (toevoegingen/wijzigingen worden vetgedrukt).
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
T0
bepalen. AlsT
een null-type is, isT0
het onderliggende type, anders isT0
gelijk aanT
. - Zoek de set van door de gebruiker gedefinieerde operators,
U
. Deze set bestaat uit:-
In
unchecked
evaluatiecontext zijn alle regelmatige declaraties vanoperator op
inT0
. -
In
checked
evaluatiecontext worden alle gecontroleerde en regelmatige declaraties vanoperator op
inT0
behalve reguliere declaraties met een paarsgewijs overeenkomendechecked operator
verklaring.
-
In
- Voor alle
operator op
declaraties inU
en alle opgeheven vormen van dergelijke operatoren, indien ten minste één operator van toepassing is (§12.4.6 - Toepasselijk functielid) met betrekking tot de lijst met argumentenA
, dan bestaat de set kandidaat-operators uit alle toepasselijke operatoren inT0
. - Als
T0
andersobject
is, dan is de set van kandidaatoperators leeg. - Anders is de set kandidaat-operators die worden geleverd door
T0
de set kandidaat-operators die worden geleverd door de directe basisklasse vanT0
, of de effectieve basisklasse vanT0
alsT0
een typeparameter is.
Er worden vergelijkbare regels toegepast tijdens het bepalen van de set kandidaat-operators in interfaces https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.
De sectie §12.8.20 wordt aangepast aan het effect dat de gecontroleerd/niet-gecontroleerde context heeft op de overbelastingsresolutie van unaire en binaire operator.
Voorbeeld 1:
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Error: Operator '*' cannot be applied to operands of type 'Int128' and 'Int128'
Int128 r6 = unchecked(lhs * rhs);
}
public static void Divide(Int128 lhs, byte rhs)
{
// Resolves to `op_Division` - it is a better match than `op_CheckedDivision`
Int128 r4 = checked(lhs / rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
// Cannot be declared in C# - missing unchecked operator, but could be declared by some other language
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
public static Int128 operator checked /(Int128 lhs, int rhs);
public static Int128 operator /(Int128 lhs, byte rhs);
}
Voorbeeld 2:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// error CS0034: Operator '+' is ambiguous on operands of type 'C2' and 'C3'
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
// Cannot be declared in C# - missing unchecked operator, but could be declared by some other language
public static C1 operator checked + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Voorbeeld 3:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// error CS0034: Operator '+' is ambiguous on operands of type 'C2' and 'C3'
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
// Cannot be declared in C# - missing unchecked operator, but could be declared by some other language
public static C2 operator checked + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Conversieoperators
Conversie checked operators
volgt de regels uit §15.10.4.
Voor een checked operator
verklaring is echter een paarsgewijze verklaring van een regular operator
vereist. Anders treedt er een compilatiefout op.
De volgende alinea
De handtekening van een conversieoperator bestaat uit het brontype en het doeltype. (Dit is de enige vorm van lid waarvoor het retourtype deelneemt aan de handtekening.) De impliciete of expliciete classificatie van een conversieoperator maakt geen deel uit van de handtekening van de operator. Een klasse of struct kan dus niet zowel een impliciete als een expliciete conversieoperator met dezelfde bron- en doeltypen declareren.
wordt aangepast zodat een type gecontroleerde en reguliere vormen van expliciete conversies met dezelfde bron- en doeltypen kan declareren. Een type mag niet zowel een impliciete als een gecontroleerde expliciete conversieoperator met dezelfde bron- en doeltypen declareren.
Verwerking van door de gebruiker gedefinieerde expliciete conversies
Het derde opsommingsteken in §10.5.5:
- Zoek de set toepasselijke door de gebruiker gedefinieerde en "lifted" conversieoperators,
U
. Deze set bestaat uit de door de gebruiker gedefinieerde en opgeheven impliciete of expliciete conversieoperators die zijn gedeclareerd door de klassen of structs inD
die worden omgezet van een type dat doorS
wordt omsloten of omvat door een type dat doorT
wordt opgenomen. AlsU
leeg is, is de conversie niet gedefinieerd en treedt er een compilatiefout op.
wordt vervangen door de volgende opsommingstekens:
- Zoek de set omzettingsoperators
U0
. Deze set bestaat uit:-
In
unchecked
evaluatiecontext worden de door de gebruiker gedefinieerde impliciete of reguliere expliciete conversieoperators gedeclareerd door de klassen of structs inD
. -
In
checked
evaluatiecontext hebben de door de gebruiker gedefinieerde impliciete of reguliere/gecontroleerde expliciete conversieoperators die zijn gedeclareerd door de klassen of structs inD
, behalve reguliere expliciete conversieoperators die koppelgewijs overeenkomendechecked operator
declaratie binnen hetzelfde declarerende type hebben.
-
In
- Zoek de set toepasselijke door de gebruiker gedefinieerde en "lifted" conversieoperators,
U
. Deze set bestaat uit de door de gebruiker gedefinieerde en opgeheven impliciete of expliciete conversie-operators inU0
die converteren van een type datS
omvat of doorS
wordt omvat naar een type dat omvat of door wordt omvat. AlsU
leeg is, is de conversie niet gedefinieerd en treedt er een compilatiefout op.
De ingeschakelde en niet-gecontroleerde operatoren sectie §11.8.20 worden aangepast aan het effect dat de ingeschakelde/niet-gecontroleerde context heeft op de verwerking van door de gebruiker gedefinieerde expliciete conversies.
Het implementeren van operators
Een checked operator
implementeert geen regular operator
en omgekeerd.
Linq Expression Trees
Checked operators
wordt ondersteund in Linq Expression Trees. Er wordt een UnaryExpression
/BinaryExpression
-knooppunt gemaakt met de bijbehorende MethodInfo
.
De volgende factory-methoden worden gebruikt:
public static UnaryExpression NegateChecked (Expression expression, MethodInfo? method);
public static BinaryExpression AddChecked (Expression left, Expression right, MethodInfo? method);
public static BinaryExpression SubtractChecked (Expression left, Expression right, MethodInfo? method);
public static BinaryExpression MultiplyChecked (Expression left, Expression right, MethodInfo? method);
public static UnaryExpression ConvertChecked (Expression expression, Type type, MethodInfo? method);
Houd er rekening mee dat C# geen ondersteuning biedt voor toewijzingen in expressiebomen, waardoor gecontroleerde incrementele/decrementele bewerkingen ook niet worden ondersteund.
Er is geen fabrieksmethode voor gecontroleerd delen. Er is een open vraag met betrekking tot dit - Checked division in Linq Expression Trees.
Dynamisch
We onderzoeken de kosten voor het toevoegen van ondersteuning voor gecontroleerde operators in dynamische aanroep in CoreCLR en streven naar een implementatie als de kosten niet te hoog zijn. Dit is een citaat uit https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md.
Nadelen
Dit voegt extra complexiteit toe aan de taal en stelt gebruikers in staat om meer soorten ingrijpende wijzigingen in hun types te introduceren.
Alternatieven
De algemene wiskundige interfaces die de bibliotheken beschikbaar willen maken, kunnen benoemde methoden (zoals AddChecked
) beschikbaar maken. Het belangrijkste nadeel is dat dit minder leesbaar/onderhoudbaar is en niet het voordeel krijgt van de regels voor taalprioriteit rond operators.
In deze sectie vindt u alternatieven die worden besproken, maar niet geïmplementeerd
Plaatsing van het checked
trefwoord
U kunt ook het checked
trefwoord naar de plaats verplaatsen vlak voor het trefwoord operator
:
public static T checked operator ++(T x) {...}
public static T checked operator --(T x) {...}
public static T checked operator -(T x) {...}
public static T checked operator +(T lhs, T rhs) {...}
public static T checked operator -(T lhs, T rhs) {...}
public static T checked operator *(T lhs, T rhs) {...}
public static T checked operator /(T lhs, T rhs) {...}
public static explicit checked operator U(T x) {...}
public static T checked I1.operator ++(T x) {...}
public static T checked I1.operator --(T x) {...}
public static T checked I1.operator -(T x) {...}
public static T checked I1.operator +(T lhs, T rhs) {...}
public static T checked I1.operator -(T lhs, T rhs) {...}
public static T checked I1.operator *(T lhs, T rhs) {...}
public static T checked I1.operator /(T lhs, T rhs) {...}
public static explicit checked I1.operator U(T x) {...}
Deze kan worden verplaatst naar de verzameling operator-modifiers:
operator_modifier
: 'public'
| 'static'
| 'extern'
| 'checked'
| operator_modifier_unsafe
;
public static checked T operator ++(T x) {...}
public static checked T operator --(T x) {...}
public static checked T operator -(T x) {...}
public static checked T operator +(T lhs, T rhs) {...}
public static checked T operator -(T lhs, T rhs) {...}
public static checked T operator *(T lhs, T rhs) {...}
public static checked T operator /(T lhs, T rhs) {...}
public static checked explicit operator U(T x) {...}
public static checked T I1.operator ++(T x) {...}
public static checked T I1.operator --(T x) {...}
public static checked T I1.operator -(T x) {...}
public static checked T I1.operator +(T lhs, T rhs) {...}
public static checked T I1.operator -(T lhs, T rhs) {...}
public static checked T I1.operator *(T lhs, T rhs) {...}
public static checked T I1.operator /(T lhs, T rhs) {...}
public static checked explicit I1.operator U(T x) {...}
unchecked
trefwoord
Er zijn suggesties gedaan om unchecked
trefwoord op dezelfde positie te ondersteunen als het checked
trefwoord met de volgende mogelijke betekenissen:
- Simpelweg om expliciet de normale aard van de operator weer te geven, of
- Misschien om een afzonderlijke variant te benoemen van een operator die in een
unchecked
-context moet worden gebruikt. De taal kan ondersteuning bieden voorop_Addition
,op_CheckedAddition
enop_UncheckedAddition
om het aantal belangrijke wijzigingen te beperken. Hierdoor wordt een andere complexiteitslaag toegevoegd die waarschijnlijk niet nodig is in de meeste code.
Operatornamen in ECMA-335
De operatornamen kunnen ook op_UnaryNegationChecked, op_AdditionChecked, op_SubtractionChecked, op_MultiplyChecked, op_DivisionCheckedzijn, met gecontroleerd aan het einde. Het lijkt er echter op dat er al een patroon is vastgesteld om de namen met het woord operator te beëindigen. Er is bijvoorbeeld een operator op_UnsignedRightShift in plaats van op_RightShiftUnsigned.
Checked operators
kunnen niet worden toegepast in een unchecked
context
De compiler kan bij het uitvoeren van het opzoeken van leden om door de gebruiker gedefinieerde kandidaatoperators binnen een unchecked
context te vinden, checked operators
negeren. Als er metagegevens worden aangetroffen die alleen een checked operator
definiëren, treedt er een compilatiefout op.
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Error: Operator '*' cannot be applied to operands of type 'Int128' and 'Int128'
Int128 r5 = unchecked(lhs * rhs);
}
}
public struct Int128
{
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
}
Complexere regels voor operator-opzoeking en overbelastingsresolutie in een checked
-context
De compiler houdt bij het uitvoeren van lidzoekacties om door de gebruiker gedefinieerde kandidaatoperators te vinden binnen een checked
-context, ook rekening met toepasselijke operators die eindigen op Checked
. Dat wil gezegd dat als de compiler probeert te zoeken naar toepasselijke functieleden voor de operator voor binaire optellen, er wordt gezocht naar zowel op_Addition
als op_AdditionChecked
. Als het enige toepasselijke functielid een checked operator
is, wordt deze gebruikt. Als zowel een regular operator
als checked operator
bestaan en even toepasselijk zijn, heeft de checked operator
de voorkeur. Als zowel een regular operator
als een checked operator
bestaat, maar de regular operator
een exacte overeenkomst is terwijl de checked operator
niet is, geeft de compiler de voorkeur aan de regular operator
.
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
}
public static void Multiply(Int128 lhs, byte rhs)
{
// Resolves to `op_Multiply` even though `op_CheckedMultiply` is also applicable
Int128 r4 = checked(lhs * rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
public static Int128 operator checked *(Int128 lhs, int rhs);
public static Int128 operator *(Int128 lhs, byte rhs);
}
Nog een andere manier om de set door de gebruiker gedefinieerde kandidaatoperators te bouwen
Oplossing voor overbelasting van unaire operator
Ervan uitgaande dat regular operator
overeenkomt met de unchecked
evaluatiecontext, komt checked operator
overeen met de checked
evaluatiecontext en een operator die geen checked
vorm heeft (bijvoorbeeld +
) overeenkomt met een van beide contexten, het eerste opsommingsteken in §12.4.4 - Eenplaatsige operator overbelasting oplossing:
- De set door de gebruiker gedefinieerde kandidaatoperators die door
X
worden verstrekt voor de bewerkingoperator op(x)
wordt bepaald aan de hand van de regels van §12.4.6 - Door de gebruiker gedefinieerde kandidaatoperators.
wordt vervangen door de volgende twee opsommingstekens:
- De set door de gebruiker gedefinieerde kandidaatoperators geleverd door
X
voor de bewerkingoperator op(x)
, die overeenkomt met de huidige ingeschakelde/uitgeschakelde context, wordt bepaald volgens de regels van door de gebruiker gedefinieerde kandidaatoperators . - Als de set door de gebruiker gedefinieerde kandidaatoperators niet leeg is, wordt dit de set kandidaatoperators voor de bewerking. Anders wordt de set kandidaatoperators die door gebruikers zijn gedefinieerd door
X
voor de bewerkingoperator op(x)
en die overeenkomt met de tegenovergestelde gecontroleerde/niet-gecontroleerde context bepaald volgens de regels van §12.4.6 - Door gebruikers gedefinieerde kandidaatoperators.
Oplossen van overbelasting van binaire operatoren
Ervan uitgaande dat regular operator
overeenkomt met unchecked
evaluatiecontext, komt checked operator
overeen met checked
evaluatiecontext en een operator die geen checked
formulier heeft (bijvoorbeeld %
) komt overeen met een van beide contexten, het eerste opsommingsteken in §12.4.5 - Overbelastingsresolutie van binaire operator:
- De set door de gebruiker gedefinieerde kandidaatoperators die worden geleverd door
X
enY
voor de bewerkingoperator op(x,y)
wordt bepaald. De set bestaat uit de samenvoeging van de kandidaat-operators die doorX
worden verstrekt en de kandidaat-operators die doorY
worden verstrekt, die elk worden bepaald volgens de regels van §12.4.6 - Door de gebruiker gedefinieerde kandidaatoperators. AlsX
enY
hetzelfde type zijn, of alsX
enY
zijn afgeleid van een gemeenschappelijk basistype, vinden gedeelde kandidaatoperators slechts één keer plaats in de gecombineerde set.
wordt vervangen door de volgende twee opsommingstekens:
- De set kandidaat-operators, gedefinieerd door de gebruiker en geleverd door
X
enY
voor de bewerkingoperator op(x,y)
, die overeenkomt met de huidige ingeschakelde/niet-ingeschakelde context, wordt bepaald. De set bestaat uit de samenvoeging van de kandidaat-operators die doorX
worden verstrekt en de kandidaat-operators die doorY
worden verstrekt, die elk worden bepaald volgens de regels van §12.4.6 - Door de gebruiker gedefinieerde kandidaatoperators. AlsX
enY
hetzelfde type zijn, of alsX
enY
zijn afgeleid van een gemeenschappelijk basistype, vinden gedeelde kandidaatoperators slechts één keer plaats in de gecombineerde set. - Als de set door de gebruiker gedefinieerde kandidaatoperators niet leeg is, wordt dit de set kandidaatoperators voor de bewerking. Anders wordt bepaald welke set kandidaatoperators door de gebruiker zijn gedefinieerd en door
X
enY
worden geleverd voor de bewerkingoperator op(x,y)
die overeenkomt met de tegenovergestelde gecontroleerde/niet-gecontroleerde context. De set bestaat uit de samenvoeging van de kandidaat-operators die doorX
worden verstrekt en de kandidaat-operators die doorY
worden verstrekt, die elk worden bepaald volgens de regels van §12.4.6 - Door de gebruiker gedefinieerde kandidaatoperators. AlsX
enY
hetzelfde type zijn, of alsX
enY
zijn afgeleid van een gemeenschappelijk basistype, vinden gedeelde kandidaatoperators slechts één keer plaats in de gecombineerde set.
Voorbeeld 1:
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = unchecked(lhs * rhs);
}
public static void Divide(Int128 lhs, byte rhs)
{
// Resolves to `op_CheckedDivision`
Int128 r4 = checked(lhs / rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
public static Int128 operator checked /(Int128 lhs, int rhs);
public static Int128 operator /(Int128 lhs, byte rhs);
}
Voorbeeld 2:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C1.op_CheckedAddition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Voorbeeld 3:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Voorbeeld 4:
class C
{
static void Add(C2 x, byte y)
{
object o;
// C1.op_CheckedAddition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C2.op_Addition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, byte y) => new C1();
}
class C2 : C1
{
public static C2 operator + (C2 x, int y) => new C2();
}
Voorbeeld 5:
class C
{
static void Add(C2 x, byte y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C1.op_Addition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, int y) => new C1();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, byte y) => new C2();
}
Verwerking van door de gebruiker gedefinieerde expliciete conversies
Ervan uitgaande dat de evaluatiecontext van regular operator
overeenkomt met die van unchecked
, en dat de evaluatiecontext van checked operator
overeenkomt met die van checked
, bevindt het derde opsommingsteken in §10.5.3 Evaluatie van door de gebruiker gedefinieerde conversieszich:
- Zoek de set toepasselijke door de gebruiker gedefinieerde en "lifted" conversieoperators,
U
. Deze set bestaat uit de door de gebruiker gedefinieerde en opgeheven impliciete of expliciete conversieoperators die zijn gedeclareerd door de klassen of structs inD
die worden omgezet van een type dat doorS
wordt omsloten of omvat door een type dat doorT
wordt opgenomen. AlsU
leeg is, is de conversie niet gedefinieerd en treedt er een compilatiefout op.
wordt vervangen door de volgende opsommingstekens:
- Zoek de set toepasselijke door de gebruiker gedefinieerde en opgeheven expliciete conversieoperators die overeenkomen met de huidige gecontroleerde/niet-gecontroleerde context,
U0
. Deze set bestaat uit de door de gebruiker gedefinieerde en opgeheven expliciete conversieoperators die zijn gedeclareerd door de klassen of structs inD
die overeenkomen met de huidige ingeschakelde/niet-gecontroleerde context en converteren van een type dat doorS
is opgenomen in een type datT
omvat of omvat. - Zoek de set van toepasselijke, door de gebruiker gedefinieerde, en expliciete conversieoperators die overeenkomen met de tegenovergestelde gecontroleerde/niet-gecontroleerde context,
U1
. AlsU0
niet leeg is, isU1
leeg. Anders bestaat deze set uit de door de gebruiker gedefinieerde en opgeheven expliciete conversieoperators die zijn gedeclareerd door de klassen of structs inD
die overeenkomen met de tegenovergestelde ingeschakelde/niet-gecontroleerde context en worden omgezet van een type dat doorS
is opgenomen in een type datT
omvat of omvat. - Zoek de set toepasselijke door de gebruiker gedefinieerde en "lifted" conversieoperators,
U
. Deze set bestaat uit operatoren uitU0
,U1
en de door de gebruiker gedefinieerde en opgeheven impliciete conversieoperatoren die zijn gedeclareerd door de klassen of structs inD
. Deze operatoren zetten om van een type datS
omvat of doorS
wordt omvat, naar een type dat omvat of door wordt omvat. AlsU
leeg is, is de conversie niet gedefinieerd en treedt er een compilatiefout op.
Een andere manier om de set gebruikergedefinieerde kandidaatoperators te bouwen
Oplossing voor overbelasting van unaire operator
Het eerste punt in de sectie §12.4.4 wordt als volgt aangepast (toevoegingen zijn vetgedrukt).
- De set door de gebruiker gedefinieerde kandidaatoperators die door
X
worden verstrekt voor de bewerkingoperator op(x)
wordt bepaald met behulp van de onderstaande regels van de sectie Door de gebruiker gedefinieerde kandidaatoperators. Als de set ten minste één operator in het ingecheckte formulier bevat, worden alle operators in normale vorm uit de set verwijderd.
De sectie §12.8.20 wordt aangepast aan het effect dat de gecontroleerde/niet-gecontroleerde context heeft op de overbelastingsresolutie van unaire operatoren.
Oplossen van overbelasting van binaire operatoren
Het eerste opsommingsteken in de sectie §12.4.5 wordt als volgt aangepast (toevoegingen zijn vetgedrukt).
- De set door de gebruiker gedefinieerde kandidaatoperators die worden geleverd door
X
enY
voor de bewerkingoperator op(x,y)
wordt bepaald. De set bestaat uit de samenvoeging van de kandidaat-operatoren die worden geleverd doorX
en de kandidaat-operators die doorY
worden geleverd, die elk worden bepaald met behulp van de regels van de sectie "Door de gebruiker gedefinieerde kandidaatoperators" hieronder. AlsX
enY
hetzelfde type zijn, of alsX
enY
zijn afgeleid van een gemeenschappelijk basistype, vinden gedeelde kandidaatoperators slechts één keer plaats in de gecombineerde set. Als de set ten minste één operator in het ingecheckte formulier bevat, worden alle operators in normale vorm uit de set verwijderd.
De gecheckte en ongecheckte operatoren §12.8.20 sectie worden aangepast om het effect te weerspiegelen dat de gecheckte/ongecheckte context heeft op de overbelastingsresolutie van binaire operatoren.
Door de gebruiker gedefinieerde kandidaatoperators
De §12.4.6 - Kandidaat door de gebruiker gedefinieerde operatoren sectie wordt als volgt aangepast. Toevoegingen zijn vetgedrukt.
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
T0
bepalen. AlsT
een null-type is, isT0
het onderliggende type, anders isT0
gelijk aanT
. - Voor alle
operator op
declaraties in hun gecontroleerde en reguliere vormen inchecked
evaluatiecontext en alleen in hun reguliere vorm inunchecked
evaluatiecontext inT0
en alle opgeheven vormen van dergelijke operatoren, als ten minste één operator van toepassing is (§12.6.4.2) met betrekking tot de argumentenlijstA
, dan bestaat de set kandidaat-operators uit al deze toepasselijke operators inT0
. - Als
T0
andersobject
is, dan is de set van kandidaatoperators leeg. - Anders is de set kandidaat-operators die worden geleverd door
T0
de set kandidaat-operators die worden geleverd door de directe basisklasse vanT0
, of de effectieve basisklasse vanT0
alsT0
een typeparameter is.
Vergelijkbare filters worden toegepast tijdens het bepalen van de set kandidaat-operators in interfaces https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.
De sectie §12.8.20 wordt aangepast om het effect dat de gecontroleerde/niet-gecontroleerde context heeft op de overbelastingsresolutie van unaire en binaire operatoren weer te geven.
Voorbeeld 1:
public class MyClass
{
public static void Add(Int128 lhs, Int128 rhs)
{
// Resolves to `op_CheckedAddition`
Int128 r1 = checked(lhs + rhs);
// Resolves to `op_Addition`
Int128 r2 = unchecked(lhs + rhs);
// Resolve to `op_Subtraction`
Int128 r3 = checked(lhs - rhs);
// Resolve to `op_Subtraction`
Int128 r4 = unchecked(lhs - rhs);
// Resolves to `op_CheckedMultiply`
Int128 r5 = checked(lhs * rhs);
// Error: Operator '*' cannot be applied to operands of type 'Int128' and 'Int128'
Int128 r5 = unchecked(lhs * rhs);
}
public static void Divide(Int128 lhs, byte rhs)
{
// Resolves to `op_CheckedDivision`
Int128 r4 = checked(lhs / rhs);
}
}
public struct Int128
{
public static Int128 operator checked +(Int128 lhs, Int128 rhs);
public static Int128 operator +(Int128 lhs, Int128 rhs);
public static Int128 operator -(Int128 lhs, Int128 rhs);
public static Int128 operator checked *(Int128 lhs, Int128 rhs);
public static Int128 operator checked /(Int128 lhs, int rhs);
public static Int128 operator /(Int128 lhs, byte rhs);
}
Voorbeeld 2:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C1.op_CheckedAddition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Voorbeeld 3:
class C
{
static void Add(C2 x, C3 y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, C3 y) => new C3();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, C1 y) => new C2();
}
class C3 : C1
{
}
Voorbeeld 4:
class C
{
static void Add(C2 x, byte y)
{
object o;
// C2.op_Addition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C2.op_Addition
o = checked(x + y);
// C2.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator checked + (C1 x, byte y) => new C1();
}
class C2 : C1
{
public static C2 operator + (C2 x, int y) => new C2();
}
Voorbeeld 5:
class C
{
static void Add(C2 x, byte y)
{
object o;
// C2.op_CheckedAddition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
static void Add2(C2 x, int y)
{
object o;
// C1.op_Addition
o = checked(x + y);
// C1.op_Addition
o = unchecked(x + y);
}
}
class C1
{
public static C1 operator + (C1 x, int y) => new C1();
}
class C2 : C1
{
public static C2 operator checked + (C2 x, byte y) => new C2();
}
Verwerking van door de gebruiker gedefinieerde expliciete conversies
Het derde opsommingsteken in §10.5.5:
- Zoek de set toepasselijke door de gebruiker gedefinieerde en "lifted" conversieoperators,
U
. Deze set bestaat uit de door de gebruiker gedefinieerde en opgeheven impliciete of expliciete conversieoperators die zijn gedeclareerd door de klassen of structs inD
die worden omgezet van een type dat doorS
wordt omsloten of omvat door een type dat doorT
wordt opgenomen. AlsU
leeg is, is de conversie niet gedefinieerd en treedt er een compilatiefout op.
wordt vervangen door de volgende opsommingstekens:
- Zoek de set toepasselijke door de gebruiker gedefinieerde en opgeheven expliciete conversieoperators,
U0
. Deze set bestaat uit de door de gebruiker gedefinieerde en uitgeheven expliciete conversieoperators, die zijn gedeclareerd door de klassen of structs inD
in hun gecontroleerde en reguliere vormen inchecked
evaluatiecontext en alleen in hun reguliere vorm inunchecked
evaluatiecontext, en die worden omgezet van een type dat doorS
wordt omvat of wordt omvat door een type dat doorT
wordt omvat of omvat. - Als
U0
ten minste één operator in het ingecheckte formulier bevat, worden alle operators in het normale formulier uit de set verwijderd. - Zoek de set toepasselijke door de gebruiker gedefinieerde en "lifted" conversieoperators,
U
. Deze set bestaat uit operatoren uitU0
en de door de gebruiker gedefinieerde en opgeheven impliciete conversieoperators die zijn gedeclareerd door de klassen of structs inD
die omzetten van een type datS
omvat of omvat wordt doorS
naar een type dat omvat of omvat wordt door . AlsU
leeg is, is de conversie niet gedefinieerd en treedt er een compilatiefout op.
De ingeschakelde en niet-gecontroleerde operatoren sectie §12.8.20 worden aangepast aan het effect dat de ingeschakelde/niet-gecontroleerde context heeft op de verwerking van door de gebruiker gedefinieerde expliciete conversies.
Ingeschakeld versus uitgeschakeld context binnen een checked operator
De compiler kan de standaardcontext van een checked operator
als gecontroleerd beschouwen. De ontwikkelaar moet expliciet unchecked
gebruiken als een deel van het algoritme niet mag deelnemen aan de checked context
. Dit werkt echter mogelijk niet goed in de toekomst als we checked
/unchecked
tokens als modifiers voor operators toestaan om de context in de hoofdtekst in te stellen. De modifier en het trefwoord kunnen elkaar tegenspreken. Ook zouden we niet hetzelfde kunnen doen (standaardcontext behandelen als uitgeschakeld) voor een regular operator
omdat dat een belangrijke wijziging zou zijn.
Niet-opgeloste vragen
Moet de taal checked
- en unchecked
-modificatoren op methoden toestaan (bijvoorbeeld static checked void M()
)?
Hierdoor kunnen nestniveaus worden verwijderd voor methoden waarvoor dit is vereist.
Gecontroleerde deling in Linq Expression Trees
Er is geen factorymethode voor het maken van een gecontroleerd afdelingsknooppunt en er is geen ExpressionType.DivideChecked
lid.
We zouden nog steeds de volgende factorymethode kunnen gebruiken om een regulier deelknooppunt te maken met MethodInfo
die naar de op_CheckedDivision
-methode verwijst.
Consumenten moeten de naam nakijken om de context af te leiden.
public static BinaryExpression Divide (Expression left, Expression right, MethodInfo? method);
Hoewel §12.8.20 artikelen een lijst bevat van /
operator als een van de operators die beïnvloed worden door de gecontroleerde/niet-gecontroleerde evaluatiecontext, heeft IL geen speciale op-code om een gecontroleerde deling uit te voeren.
Compiler maakt altijd gebruik van de fabrieksmethode die vandaag nog niet aan de context is aangepast.
Voorstel: Door de gebruiker gedefinieerde deling wordt niet ondersteund in Linq Expression Trees.
(Opgelost) Moeten we impliciete gecontroleerde conversieoperators ondersteunen?
In het algemeen mogen impliciete conversieoperators niet worden gegooid.
Voorstel: Nee.
Resolutie: goedgekeurd - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md#checked-implicit-conversions
Ontwerpvergaderingen
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-14.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-23.md
C# feature specifications