Dela via


Granskade användardefinierade operatorer

Not

Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.

Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader fångas upp i de relevanta LDM-anteckningarna (Language Design Meeting) .

Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.

Sammanfattning

C# bör ha stöd för att definiera checked varianter av följande användardefinierade operatorer så att användarna kan välja eller inte använda spillbeteende efter behov:

  • De ++ och -- unära operatorerna §12.8.16 och §12.9.6.
  • Den - unary operatören §12.9.3.
  • Operatorerna +, -, *och / binära operatorer §12.10.
  • Explicita konverteringsoperatorer.

Motivation

Det finns inget sätt för en användare att deklarera en typ och att stödja både kontrollerade och okontrollerade versioner av en operator. Detta gör det svårt att porta olika algoritmer för att använda de föreslagna generic math gränssnitt som exponeras av biblioteksteamet. På samma sätt gör detta det omöjligt att exponera en typ som Int128 eller UInt128 utan att språket samtidigt tillhandahåller sitt eget stöd för att undvika förändringar som bryter kompatibilitet.

Detaljerad design

Syntax

Grammatik vid operatorer (§15.10) kommer att justeras för att tillåta checked nyckelord att följa direkt efter operator nyckelord precis före 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 ')'
    ;    

Till exempel:

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

För korthet nedan kallas en operator med nyckelordet checked för en checked operator och en operator utan det kallas för en regular operator. Dessa villkor gäller inte för operatorer som inte har ett checked formulär.

Semantik

En användardefinierad checked operator förväntas utlösa ett undantag när resultatet av en åtgärd är för stort för att representeras i måltypen. Vad det innebär att vara för stor beror faktiskt på måldatatypernas natur och fastställs inte av språket. Vanligtvis är undantaget ett System.OverflowException, men språket har inga specifika krav för detta.

En användardefinierad regular operator förväntas inte utlösa ett undantag när resultatet av en åtgärd är för stort för att representeras i måltypen. I stället förväntas den returnera en instans som representerar ett trunkerat resultat. Vad det innebär att vara för stor och trunkeras beror egentligen på måltypen och är inte föreskrivet av språket.

Alla befintliga användardefinierade operatorer som har formen checked som stöds tillhör kategorin regular operators. Det är underförstått att många av dem sannolikt inte följer de semantik som anges ovan, men för semantisk analys antar kompilatorn att de är det.

Markerad kontra omarkerad kontext i en checked operator

Markerad/avmarkerad kontext i brödtexten i en checked operator påverkas inte av förekomsten av nyckelordet checked. Med andra ord är kontexten densamma som omedelbart i början av operatordeklarationen. Utvecklaren måste uttryckligen byta kontext om en del av deras algoritm inte kan förlita sig på standardkontexten.

Namn i metadata

Avsnitt "I.10.3.1 Unary operators" i ECMA-335 justeras så att op_CheckedIncrement, op_CheckedDecrement, op_CheckedUnaryNegation som namn på metoder som implementerar kontrollerade ++, -- och - unary operatorer.

Avsnitt "I.10.3.2 Binära operatorer" för ECMA-335 justeras för att inkludera op_CheckedAddition, op_CheckedSubtraction, op_CheckedMultiply, op_CheckedDivision som namn på metoder som implementerar kontrollerade +, -, *och / binära operatorer.

Avsnitt "I.10.3.3 Konverteringsoperatorer" i ECMA-335 justeras så att op_CheckedExplicit inkluderas som namn på en metod som implementerar den kontrollerade explicita konverteringsoperatorn.

Unary-operatorer

Följ Unary checked operators reglerna från §15.10.2.

En checked operator-deklaration kräver också en parvis deklaration av en regular operator (returtypen ska också matcha). Ett kompileringsfel inträffar annars.

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

Binära operatorer

Binära checked operators följer reglerna från §15.10.3.

En checked operator-deklaration kräver också en parvis deklaration av en regular operator (returtypen ska också matcha). Ett kompileringsfel inträffar annars.

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

Användardefinierade kandidatoperatorer

Avsnittet Kandidatanvändare (§12.4.6) justeras enligt följande (tillägg/ändringar är i fetstil).

Givet en typ T och en operation operator op(A), där op är en överlagbar operator och A är en argumentlista, bestäms uppsättningen av användardefinierade operatorer som tillhandahålls av T för operator op(A) på följande sätt:

  • Fastställ typen T0. Om T är en nullbar typ är T0 dess underliggande typ, annars är T0 lika med T.
  • Leta reda på uppsättningen med användardefinierade operatorer U. Den här uppsättningen består av:
    • I en unchecked utvärderingskontext finns operator op alla regelbundna deklarationer i T0.
    • I den checked utvärderingskontexten granskas alla kontrollerade och regelbundna operator op-deklarationer i T0, förutom de regelbundna deklarationerna som har en parvis matchande checked operator-deklaration.
  • För alla operator op deklarationer i U och alla upplyfta former av sådana operatörer, om minst en operatör är tillämplig (§12.4.6 - Tillämplig funktionsmedlem) med avseende på argumentlistan A, består uppsättningen av kandidatoperatorer av alla sådana tillämpliga operatörer i T0.
  • Om T0 är objectär annars uppsättningen med kandidatoperatorer tom.
  • Annars är uppsättningen kandidatoperatorer som tillhandahålls av T0 uppsättningen kandidatoperatorer som tillhandahålls av den direkta basklassen för T0eller den verksamma basklassen för T0 om T0 är en typparameter.

Liknande regler tillämpas när du fastställer uppsättningen kandidatoperatorer i gränssnitt https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.

Avsnittet §12.8.20 justeras för att återspegla den effekt som den kontrollerade/okontrollerade kontexten har på unär och binär operatoröverbelastningsupplösning.

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

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

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

Konverteringsoperatorer

Konvertering checked operators följer reglerna i §15.10.4.

En checked operator-deklaration kräver dock en parvis förklaring av en regular operator. Ett kompileringsfel inträffar annars.

Följande stycke

Signaturen för en konverteringsoperator består av källtypen och måltypen. (Detta är den enda medlemsform som returtypen deltar i signaturen för.) Den implicita eller explicita klassificeringen av en konverteringsoperator ingår inte i operatorns signatur. Därför kan en klass eller struct inte deklarera både en implicit och en explicit konverteringsoperator med samma käll- och måltyper.

justeras så att en typ kan deklarera kontrollerade och regelbundna former av explicita konverteringar med samma käll- och måltyper. En typ får inte deklarera både en implicit och en markerad explicit konverteringsoperator med samma käll- och måltyper.

Bearbetning av användardefinierade explicita konverteringar

Den tredje punkten i §10.5.5:

  • Hitta mängden av tillämpliga användardefinierade och lyfta konverteringsoperatorer U. Den här uppsättningen består av användardefinierade och förhöjda implicita eller explicita konverteringsoperatorer som deklarerats av klasserna eller structarna i D som konverterar från en typ som omfattar eller ingår i S till en typ som omfattar eller ingår i T. Om U är tom är konverteringen odefinierad och ett kompileringsfel inträffar.

ersätts med följande punktlistor:

  • Hitta uppsättningen konverteringsoperatorer, U0. Den här uppsättningen består av:
    • I unchecked utvärderingskontext deklareras de användardefinierade implicita eller vanliga explicita konverteringsoperatorerna av klasserna eller structarna i D.
    • I checked utvärderingskontext har de användardefinierade implicita eller regelbundna/kontrollerade explicita konverteringsoperatorerna deklarerats av klasserna eller structarna i D förutom vanliga explicita konverteringsoperatorer som har parvis matchning checked operator deklaration inom samma deklareringstyp.
  • Hitta mängden av tillämpliga användardefinierade och lyfta konverteringsoperatorer U. Den här uppsättningen består av användardefinierade och lyfta implicita eller explicita konverteringsoperatorer i U0 som konverterar från en typ som omfattar eller omfattas av S till en typ som omfattar eller omfattas av T. Om U är tom är konverteringen odefinierad och ett kompileringsfel inträffar.

Avsnittet Kontrollerade och avmarkerade operatorer §11.8.20 justeras för att återspegla den effekt som den markerade/okontrollerade kontexten har på bearbetningen av användardefinierade explicita konverteringar.

Implementera operatorer

En checked operator implementerar inte en regular operator och vice versa.

Linq Uttrycksträd

Checked operators stöds i Linq Expression Trees. En UnaryExpression/BinaryExpression nod skapas med motsvarande MethodInfo. Följande fabriksmetoder används:

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

Observera att C# inte stöder tilldelningar i uttrycksträd, därför stöds inte kontrollerade inkrement/dekrement heller.

Det finns ingen fabriksmetod för kontrollerad uppdelning. Det finns en öppen fråga om detta – Kontrollerad division i Linq-uttrycksträd.

Dynamisk

Vi kommer att undersöka kostnaden för att lägga till stöd för kontrollerade operatörer i dynamiskt anrop i CoreCLR och genomföra en implementering om kostnaden inte är för hög. Det här är ett citat från https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md.

Nackdelar

Detta tillför ytterligare komplexitet till språket och gör det möjligt för användare att introducera fler typer av icke-bakåtkompatibla ändringar i sina datatyper.

Alternativ

De allmänna matematiska gränssnitt som biblioteken planerar att exponera kan exponera namngivna metoder (till exempel AddChecked). Den främsta nackdelen är att detta är mindre läsbart/underhållsbart och inte får fördelen med språkprioritetsreglerna kring operatorer.

Det här avsnittet innehåller alternativ som diskuteras, men som inte implementeras

Placering av nyckelordet checked

Alternativt kan nyckelordet checked flyttas till platsen precis före nyckelordet 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) {...}

Eller så kan den flyttas till gruppen av operatörsmodifierare.

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 nyckelord

Det fanns förslag för att stödja unchecked nyckelord på samma position som nyckelordet checked med följande möjliga betydelser:

  • Bara för att uttryckligen återspegla den regelbundna karaktären hos operatören, eller
  • Kanske för att utse en specifik egenskap hos en operator som ska användas i unchecked-sammanhang. Språket kan hantera op_Addition, op_CheckedAdditionoch op_UncheckedAddition för att begränsa antalet oförenliga ändringar. Detta lägger till ytterligare ett lager av komplexitet som sannolikt inte är nödvändigt i de flesta kod.

Operatornamn i ECMA-335

Operatornamnen kan också vara op_UnaryNegationChecked, op_AdditionChecked, op_SubtractionChecked, op_MultiplyChecked, op_DivisionChecked, och med Checked i slutet. Det verkar dock som om det redan finns ett mönster som har upprättats för att avsluta namnen med operatorordet. Det finns till exempel en op_UnsignedRightShift operator i stället för op_RightShiftUnsigned operator.

Checked operators är inte tillämpligt i en unchecked kontext

När kompilatorn utför medlemssökning för att hitta kandidatanvändardefinierade operatorer i en unchecked kontext kan den ignorera checked operators. Om metadata påträffas som bara definierar en checked operatoruppstår ett kompileringsfel.

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

Mer komplexa regler för operatörsuppslag och överlagringsupplösning i ett checked-sammanhang

Kompilatorn, vid utförandet av medlemssökning för att hitta kandidatanvändardefinierade operatorer inom ett checked-sammanhang, kommer också att ta hänsyn till tillämpliga operatorer som slutar med Checked. Om kompilatorn försöker hitta tillämpliga funktionsmedlemmar för operatorn för binär addition letar den efter både op_Addition och op_AdditionChecked. Om den enda tillämpliga funktionsmedlemmen är en checked operatoranvänds den. Om både en regular operator och checked operator finns och är lika tillämpliga kommer checked operator att föredras. Om både en regular operator och en checked operator finns men regular operator är en exakt matchning medan checked operator inte är det, föredrar kompilatorn 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);
}

Ännu ett sätt att skapa uppsättningen med användardefinierade kandidatoperatorer

Unär operator-överbelastningslösning

Om vi antar att regular operator matchar unchecked utvärderingskontext, att checked operator matchar checked utvärderingskontext och att en operator som inte har checked form (till exempel +) matchar någon av utvärderingskontexterna, så gäller den första punkten i §12.4.4 – Unär operatoröverlagringsresolution.

kommer att ersättas med följande två punktlistor:

  • Uppsättningen med användardefinierade operatorer som tillhandahålls av X för åtgärden operator op(x)som matchar den aktuella markerade/avmarkerade kontexten bestäms med hjälp av reglerna för kandidatanvändardefinierade operatorer.
  • Om uppsättningen med användardefinierade kandidatoperatorer inte är tom blir detta uppsättningen kandidatoperatorer för åtgärden. I annat fall bestäms uppsättningen med användardefinierade operatorer som tillhandahålls av X för åtgärden operator op(x)som matchar den motsatta kontrollerade/okontrollerade kontexten med hjälp av reglerna i §12.4.6 – Kandidatanvändardefinierade operatorer.

Binär operatoröverbelastningsupplösning

Under förutsättning att regular operator matchar (det vill säga har samma utvärderingskontext som) unchecked, checked operator matchar (det vill säga har samma utvärderingskontext som) checked, och en operator som inte har ett checked-formulär (till exempel %) matchar antingen kontexten, innebär det första punkten i §12.4.5 – Binär operatoröverbelastningslösning:

  • Uppsättningen av användardefinierade operatorer som tillhandahålls av X och Y för operationen operator op(x,y) fastställs. Uppsättningen består av en union av de kandidatoperatörer som tillhandahålls av X och de kandidatoperatorer som tillhandahålls av Y, som var och en bestäms med hjälp av reglerna i §12.4.6 - Kandidatanvändardefinierade operatörer. Om X och Y är av samma typ, eller om X och Y härleds från en gemensam bastyp, uppstår endast delade kandidatoperatorer i den kombinerade uppsättningen en gång.

kommer att ersättas med följande två punktlistor:

  • Uppsättningen med kandidat-användardefinierade operatorer som tillhandahålls av X och Y för operationen operator op(x,y), och som matchar det aktuella kontextläget (markerat/omarkerat), fastställs. Uppsättningen består av en union av de kandidatoperatörer som tillhandahålls av X och de kandidatoperatorer som tillhandahålls av Y, som var och en bestäms med hjälp av reglerna i §12.4.6 - Kandidatanvändardefinierade operatörer. Om X och Y är av samma typ, eller om X och Y härleds från en gemensam bastyp, uppstår endast delade kandidatoperatorer i den kombinerade uppsättningen en gång.
  • Om uppsättningen med användardefinierade kandidatoperatorer inte är tom blir detta uppsättningen kandidatoperatorer för åtgärden. I annat fall bestäms uppsättningen med användardefinierade kandidatoperatorer som tillhandahålls av X och Y för åtgärden operator op(x,y)som matchar den motsatta markerade/avmarkerade kontexten. Uppsättningen består av en union av de kandidatoperatörer som tillhandahålls av X och de kandidatoperatorer som tillhandahålls av Y, som var och en bestäms med hjälp av reglerna i §12.4.6 - Kandidatanvändardefinierade operatörer. Om X och Y är av samma typ, eller om X och Y härleds från en gemensam bastyp, uppstår endast delade kandidatoperatorer i den kombinerade uppsättningen en gång.
Exempel nr 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);
}
Exempel nr 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
{
}
Exempel nr 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
{
}
Exempel nr 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();
}
Exempel nr 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();
}

Bearbetning av användardefinierade explicita konverteringar

Förutsatt att regular operator motsvarar unchecked utvärderingskontext och checked operator motsvarar checked utvärderingskontext, motsvarar den tredje punkten i §10.5.3 Utvärdering av användardefinierade konverteringar:

  • Hitta mängden av tillämpliga användardefinierade och lyfta konverteringsoperatorer U. Den här uppsättningen består av användardefinierade och förhöjda implicita eller explicita konverteringsoperatorer som deklarerats av klasserna eller structarna i D som konverterar från en typ som omfattar eller ingår i S till en typ som omfattar eller ingår i T. Om U är tom är konverteringen odefinierad och ett kompileringsfel inträffar.

ersätts med följande punktlistor:

  • Leta reda på uppsättningen med tillämpliga användardefinierade och upplyfta explicita konverteringsoperatorer som matchar den aktuella kontrollerade/okontrollerade kontexten, U0. Den här uppsättningen består av användardefinierade och hävda explicita konverteringsoperatorer som deklarerats av klasserna eller strukturerna i D som matchar den aktuella kontrollerade/okontrollerade kontexten och konverterar från en typ som omfattar eller omfattas av S till en typ som omfattar eller omfattas av T.
  • Hitta uppsättningen med tillämpliga användardefinierade och upplyfta explicita konverteringsoperatorer som matchar motsvarande kontrollerade/okontrollerade sammanhang, U1. Om U0 inte är tom är U1 tom. I annat fall består den här uppsättningen av användardefinierade och lyfta explicita konverteringsoperatorer som deklarerats av klasserna eller strukturerna i D som följer den motsatta markerade/avmarkerade kontexten och konverterar från en typ som omfattar eller omfattas av S till en typ som omfattar eller omfattas av T.
  • Hitta mängden av tillämpliga användardefinierade och lyfta konverteringsoperatorer U. Den här uppsättningen består av operatorer från U0, U1och de användardefinierade och förhöjda implicita konverteringsoperatorerna som deklarerats av klasserna eller structarna i D som konverterar från en typ som antingen omfattar eller omfattas av S till en typ som antingen omfattar eller omfattas av T. Om U är tom är konverteringen odefinierad och ett kompileringsfel inträffar.

Ännu ett annat sätt att skapa uppsättningen med användardefinierade kandidatoperatorer

Unär operator-överbelastningslösning

Den första punkten i avsnittet §12.4.4 justeras enligt följande (tilläggen är i fetstil).

  • Uppsättningen med användardefinierade kandidatoperatorer som tillhandahålls av X för åtgärden operator op(x) bestäms med hjälp av reglerna i avsnittet "Kandidatanvändardefinierade operatorer" nedan. Om uppsättningen innehåller minst en operator i kontrollerad form, tas alla operatorer i standardform bort från uppsättningen.

Avsnittet §12.8.20 justeras för att återspegla den effekt som kontrollerad/okontrollerad kontext har på överbelastningsupplösning för unära operatorer.

Binär operatoröverbelastningsupplösning

Den första punkten i avsnittet §12.4.5 kommer att justeras på följande sätt (tilläggen är markerade med fetstil).

  • Uppsättningen av användardefinierade operatorer som tillhandahålls av X och Y för operationen operator op(x,y) fastställs. Uppsättningen består av en union av de kandidatoperatorer som tillhandahålls av X och de kandidatoperatorer som tillhandahålls av Y, som var och en bestäms med hjälp av reglerna i avsnittet "Kandidatanvändardefinierade operatorer" nedan. Om X och Y är av samma typ, eller om X och Y härleds från en gemensam bastyp, uppstår endast delade kandidatoperatorer i den kombinerade uppsättningen en gång. Om uppsättningen innehåller minst en operator i kontrollerad form, tas alla operatorer i standardform bort från uppsättningen.

Avsnittet Kontrollerade och avmarkerade operatorer §12.8.20 kommer att justeras för att återspegla effekten som den kontrollerade/icke-kontrollerade kontexten har på binär operatoröverbelastningslösning.

Användardefinierade kandidatoperatorer

§12.4.6 – Kandidatanvändardefinierade operatorer kommer att justeras enligt följande (tilläggen är i fetstil).

Givet en typ T och en operation operator op(A), där op är en överlagbar operator och A är en argumentlista, bestäms uppsättningen av användardefinierade operatorer som tillhandahålls av T för operator op(A) på följande sätt:

  • Fastställ typen T0. Om T är en nullbar typ är T0 dess underliggande typ, annars är T0 lika med T.
  • För alla operator op deklarationer i sina kontrollerade och regelbundna former i utvärderingssammanhang checked och endast i sin regelbundna form i utvärderingssammanhang unchecked i T0, samt i alla hävda former av sådana operatorer, om minst en operator är tillämplig (§12.6.4.2) i relation till argumentlistan A, består uppsättningen av kandidatoperatorer av alla sådana tillämpliga operatorer i T0.
  • Om T0 är objectär annars uppsättningen med kandidatoperatorer tom.
  • Annars är uppsättningen kandidatoperatorer som tillhandahålls av T0 uppsättningen kandidatoperatorer som tillhandahålls av den direkta basklassen för T0eller den verksamma basklassen för T0 om T0 är en typparameter.

Liknande filtrering tillämpas när du fastställer uppsättningen kandidatoperatorer i gränssnitt https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.

Avsnittet §12.8.20 kommer att justeras för att återspegla den effekt som den kontrollerade/okontrollerade kontexten har på överbelastningslösningen för unära och binära operatorer.

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

Bearbetning av användardefinierade explicita konverteringar

Den tredje punkten i §10.5.5:

  • Hitta mängden av tillämpliga användardefinierade och lyfta konverteringsoperatorer U. Den här uppsättningen består av användardefinierade och förhöjda implicita eller explicita konverteringsoperatorer som deklarerats av klasserna eller structarna i D som konverterar från en typ som omfattar eller ingår i S till en typ som omfattar eller ingår i T. Om U är tom är konverteringen odefinierad och ett kompileringsfel inträffar.

ersätts med följande punktlistor:

  • Hitta uppsättningen av tillämpliga användar-definierade och förhöjda explicita konverteringsoperatorer U0. Den här uppsättningen består av användardefinierade och lyfta explicita konverteringsoperatorer som deklareras av klasserna eller structarna i Di sina kontrollerade och regelbundna former i checked utvärderingskontext och endast i sina regelbundna former i unchecked utvärderingskontext och konverterar från en typ som omfattar eller omfattas av S till en typ som omfattar eller omfattas av T.
  • Om U0 innehåller minst en operator i markerad form tas alla operatorer i vanlig form bort från mängden.
  • Hitta mängden av tillämpliga användardefinierade och lyfta konverteringsoperatorer U. Den här uppsättningen består av operatorer från U0och de användardefinierade och överlastade implicita konverteringsoperatorerna som deklareras av klasserna eller structarna i D som konverterar från en typ som omfattar eller omfattas av S till en typ som omfattar eller omfattas av T. Om U är tom är konverteringen odefinierad och ett kompileringsfel inträffar.

Avsnittet Kontrollerade och avmarkerade operatorer §12.8.20 justeras för att återspegla den effekt som den markerade/okontrollerade kontexten har på bearbetningen av användardefinierade explicita konverteringar.

Markerad kontra omarkerad kontext i en checked operator

Kompilatorn kan behandla standardkontexten för en checked operator som markerad. Utvecklaren måste uttryckligen använda unchecked om en del av deras algoritm inte ska delta i checked context. Detta kanske dock inte fungerar bra i framtiden om vi börjar tillåta checked/unchecked token som modifierare på operatorer för att ange kontexten i brödtexten. Modifieraren och nyckelordet kan motsäga varandra. Dessutom skulle vi inte kunna göra samma sak (behandla standardkontexten som avmarkerad) för en regular operator eftersom det skulle orsaka en oförenlig förändring.

Olösta frågor

Ska språket tillåta checked och unchecked modifierare på metoder (t.ex. static checked void M())? Detta skulle göra det möjligt att ta bort kapslingsnivåer för metoder som kräver det.

Kontrollerad division i LINQ-uttrycksträd

Det finns ingen fabriksmetod för att skapa en kontrollerad divisionsnod och det finns ingen ExpressionType.DivideChecked medlem. Vi kan fortfarande använda följande fabriksmetod för att skapa en vanlig delningsnod med MethodInfo som pekar på metoden op_CheckedDivision. Konsumenterna måste kontrollera namnet för att härleda kontexten.

public static BinaryExpression Divide (Expression left, Expression right, MethodInfo? method);

Observera att även om §12.8.20 avsnitt innehåller / operator som en av de operatörerna som påverkas av kontrollerad/okontrollerad utvärderingskontext, har IL ingen särskild op-kod för att utföra kontrollerad division. Kompilatorn använder alltid fabriksmetoden oavsett kontext idag.

Förslag: Markerad användardefinierad delning stöds inte i Linq Expression Trees.

(Löst) Ska vi stödja implicita kontrollerade konverteringsoperatorer?

I allmänhet är det inte meningen att implicita konverteringsoperatorer ska kasta.

Förslag: Nej.

lösning: godkänd – https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md#checked-implicit-conversions

Designa möten

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