Delen via


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:

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 T0bepalen. Als T een null-type is, is T0 het onderliggende type, anders is T0 gelijk aan T.
  • Zoek de set van door de gebruiker gedefinieerde operators, U. Deze set bestaat uit:
    • In unchecked evaluatiecontext zijn alle regelmatige declaraties van operator op in T0.
    • In checked evaluatiecontext worden alle gecontroleerde en regelmatige declaraties van operator op in T0 behalve reguliere declaraties met een paarsgewijs overeenkomende checked operator verklaring.
  • Voor alle operator op declaraties in U 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 argumenten A, dan bestaat de set kandidaat-operators uit alle toepasselijke operatoren in T0.
  • Als T0 anders objectis, 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 van T0, of de effectieve basisklasse van T0 als T0 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 operatorvereist. 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 in D die worden omgezet van een type dat door S wordt omsloten of omvat door een type dat door Twordt opgenomen. Als U 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 in D.
    • In checked evaluatiecontext hebben de door de gebruiker gedefinieerde impliciete of reguliere/gecontroleerde expliciete conversieoperators die zijn gedeclareerd door de klassen of structs in D, behalve reguliere expliciete conversieoperators die koppelgewijs overeenkomende checked operator declaratie binnen hetzelfde declarerende type hebben.
  • 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 in U0 die converteren van een type dat S omvat of door S wordt omvat naar een type dat omvat of door wordt omvat. Als U 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 voor op_Addition, op_CheckedAdditionen op_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 operatorsnegeren. Als er metagegevens worden aangetroffen die alleen een checked operatordefinië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 operatoris, 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:

wordt vervangen door de volgende twee opsommingstekens:

  • De set door de gebruiker gedefinieerde kandidaatoperators geleverd door X voor de bewerking operator 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 bewerking operator 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 en Y voor de bewerking operator op(x,y) wordt bepaald. De set bestaat uit de samenvoeging van de kandidaat-operators die door X worden verstrekt en de kandidaat-operators die door Yworden verstrekt, die elk worden bepaald volgens de regels van §12.4.6 - Door de gebruiker gedefinieerde kandidaatoperators. Als X en Y hetzelfde type zijn, of als X en Y 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 en Y voor de bewerking operator 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 door X worden verstrekt en de kandidaat-operators die door Yworden verstrekt, die elk worden bepaald volgens de regels van §12.4.6 - Door de gebruiker gedefinieerde kandidaatoperators. Als X en Y hetzelfde type zijn, of als X en Y 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 en Y worden geleverd voor de bewerking operator op(x,y)die overeenkomt met de tegenovergestelde gecontroleerde/niet-gecontroleerde context. De set bestaat uit de samenvoeging van de kandidaat-operators die door X worden verstrekt en de kandidaat-operators die door Yworden verstrekt, die elk worden bepaald volgens de regels van §12.4.6 - Door de gebruiker gedefinieerde kandidaatoperators. Als X en Y hetzelfde type zijn, of als X en Y 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 in D die worden omgezet van een type dat door S wordt omsloten of omvat door een type dat door Twordt opgenomen. Als U 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 in D die overeenkomen met de huidige ingeschakelde/niet-gecontroleerde context en converteren van een type dat door S is opgenomen in een type dat Tomvat of omvat.
  • Zoek de set van toepasselijke, door de gebruiker gedefinieerde, en expliciete conversieoperators die overeenkomen met de tegenovergestelde gecontroleerde/niet-gecontroleerde context, U1. Als U0 niet leeg is, is U1 leeg. Anders bestaat deze set uit de door de gebruiker gedefinieerde en opgeheven expliciete conversieoperators die zijn gedeclareerd door de klassen of structs in D die overeenkomen met de tegenovergestelde ingeschakelde/niet-gecontroleerde context en worden omgezet van een type dat door S is opgenomen in een type dat Tomvat of omvat.
  • Zoek de set toepasselijke door de gebruiker gedefinieerde en "lifted" conversieoperators, U. Deze set bestaat uit operatoren uit U0, U1en de door de gebruiker gedefinieerde en opgeheven impliciete conversieoperatoren die zijn gedeclareerd door de klassen of structs in D. Deze operatoren zetten om van een type dat S omvat of door S wordt omvat, naar een type dat omvat of door wordt omvat. Als U 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 bewerking operator 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 en Y voor de bewerking operator op(x,y) wordt bepaald. De set bestaat uit de samenvoeging van de kandidaat-operatoren die worden geleverd door X en de kandidaat-operators die door Yworden geleverd, die elk worden bepaald met behulp van de regels van de sectie "Door de gebruiker gedefinieerde kandidaatoperators" hieronder. Als X en Y hetzelfde type zijn, of als X en Y 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 T0bepalen. Als T een null-type is, is T0 het onderliggende type, anders is T0 gelijk aan T.
  • Voor alle operator op declaraties in hun gecontroleerde en reguliere vormen in checked evaluatiecontext en alleen in hun reguliere vorm in unchecked evaluatiecontext in T0 en alle opgeheven vormen van dergelijke operatoren, als ten minste één operator van toepassing is (§12.6.4.2) met betrekking tot de argumentenlijst A, dan bestaat de set kandidaat-operators uit al deze toepasselijke operators in T0.
  • Als T0 anders objectis, 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 van T0, of de effectieve basisklasse van T0 als T0 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 in D die worden omgezet van een type dat door S wordt omsloten of omvat door een type dat door Twordt opgenomen. Als U 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 in Din hun gecontroleerde en reguliere vormen in checked evaluatiecontext en alleen in hun reguliere vorm in unchecked evaluatiecontext, en die worden omgezet van een type dat door S wordt omvat of wordt omvat door een type dat door Twordt 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 uit U0en de door de gebruiker gedefinieerde en opgeheven impliciete conversieoperators die zijn gedeclareerd door de klassen of structs in D die omzetten van een type dat S omvat of omvat wordt door S naar een type dat omvat of omvat wordt door . Als U 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