Udostępnij za pośrednictwem


Sprawdzono operatory zdefiniowane przez użytkownika

Notatka

Ten artykuł jest specyfikacją funkcji. Specyfikacja służy jako dokument projektowy dla funkcji. Zawiera proponowane zmiany specyfikacji wraz z informacjami wymaganymi podczas projektowania i opracowywania funkcji. Te artykuły są publikowane do momentu sfinalizowania proponowanych zmian specyfikacji i włączenia ich do obecnej specyfikacji ECMA.

Mogą wystąpić pewne rozbieżności między specyfikacją funkcji a ukończoną implementacją. Te różnice są przechwytywane w odpowiednich spotkania projektowego języka (LDM).

Więcej informacji na temat procesu wdrażania specyfikacji funkcji można znaleźć w standardzie języka C# w artykule dotyczącym specyfikacji .

Streszczenie

Język C# powinien obsługiwać definiowanie wariantów checked następujących operatorów zdefiniowanych przez użytkownika, dzięki czemu użytkownicy mogą opcjonalnie włączać lub wyłączać zachowanie przepełnienia.

Motywacja

Nie ma możliwości dla użytkownika, aby zadeklarować typ i obsługiwać zarówno sprawdzane, jak i nieznakowane wersje operatora. Utrudni to przenoszenie różnych algorytmów do używania proponowanych interfejsów generic math udostępnianych przez zespół bibliotek. Podobnie uniemożliwia to uwidocznienie typu, takiego jak Int128 lub UInt128 bez jednoczesnego wysyłania własnego wsparcia przez język, aby uniknąć zmian powodujących niezgodność.

Szczegółowy projekt

Składnia

Gramatyka u operatorów (§15.10) zostanie dostosowana tak, aby zezwolić na checked słowo kluczowe po operator słowie kluczowym, bezpośrednio przed tokenem operatora.

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

Na przykład:

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

W przypadku zwięzłości poniżej operator ze słowem kluczowym checked jest określany jako checked operator, a operator bez niego jest określany jako regular operator. Te warunki nie mają zastosowania do operatorów, które nie mają checked formularza.

Semantyka

Oczekuje się, że checked operator zdefiniowana przez użytkownika zgłosi wyjątek, gdy wynik operacji jest zbyt duży do reprezentowania w typie docelowym. To, co oznacza bycie zbyt dużym, faktycznie zależy od natury typu docelowego i nie jest określone przez język. Zazwyczaj zgłaszany wyjątek to System.OverflowException, ale język nie ma żadnych konkretnych wymagań w tej kwestii.

Oczekuje się, że regular operator zdefiniowana przez użytkownika nie zgłosi wyjątku, gdy wynik operacji jest zbyt duży do reprezentowania w typie docelowym. Zamiast tego spodziewa się zwrócić instancję reprezentującą obcięty wynik. To, co oznacza być zbyt dużym i obciętym, faktycznie zależy od charakteru typu docelowego i nie jest określone przez język.

Wszystkie istniejące operatory zdefiniowane przez użytkownika, które będą miały obsługiwaną postać checked, należą do kategorii regular operators. Rozumie się, że wiele z nich prawdopodobnie nie będzie stosować semantyki określonych powyżej, ale w celu analizy semantycznej kompilator zakłada, że są.

Zaznaczone a niezaznaczone konteksty w checked operator

Kontekst zaznaczony/niezaznaczony w treści checked operator nie jest wpływany przez obecność słowa kluczowego checked. Innymi słowy kontekst jest taki sam jak na początku deklaracji operatora. Deweloper musiałby jawnie przełączyć kontekst, jeśli część algorytmu nie może polegać na kontekście domyślnym.

Nazwy w metadanych

Sekcja I.10.3.1 dotycząca operatorów jednoargumentowych w ECMA-335 zostanie dostosowana w celu uwzględnienia nazw metod takich jak op_CheckedIncrement, op_CheckedDecrement, op_CheckedUnaryNegation, które implementują sprawdzone operatory jednoargumentowe ++, -- i -.

Sekcja "Operatory binarne I.10.3.2" ECMA-335 zostaną dostosowane w celu uwzględnienia op_CheckedAddition, op_CheckedSubtraction, op_CheckedMultiply, op_CheckedDivision jako nazw metod implementowania sprawdzonych +, -, *i / operatorów binarnych.

Sekcja "I.10.3.3 Operatory konwersji" ECMA-335 zostanie dostosowana, aby uwzględnić op_CheckedExplicit jako nazwę metody implementującej sprawdzony operator jawnej konwersji.

Operatory jednoargumentowe

checked operators jednoargumentowe przestrzega zasad z §15.10.2.

Ponadto deklaracja checked operator wymaga deklaracji regular operator w parach (typ zwracany powinien również być zgodny). W przeciwnym razie występuje błąd czasu kompilacji.

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

Operatory binarne

Binarne checked operators przestrzegają zasad z §15.10.3.

Ponadto deklaracja checked operator wymaga deklaracji regular operator w parach (typ zwracany powinien również być zgodny). W przeciwnym razie występuje błąd czasu kompilacji.

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

Operatory kandydujące zdefiniowane przez użytkownika

Operatory użytkowników-kandydatów (§12.4.6) zostaną dostosowane w następujący sposób (dodatki/zmiany są pogrubione).

Biorąc pod uwagę typ T i operację operator op(A), gdzie op jest operatorem przeciążalnym, a A jest listą argumentów, zestaw operatorów zdefiniowanych przez użytkownika dostarczonych przez T dla operator op(A) jest określany w następujący sposób:

  • Określ typ T0. Jeśli T jest typem dopuszczającym wartości null, T0 jest jego typem podstawowym, w przeciwnym razie T0 jest równy T.
  • Znajdź zestaw operatorów zdefiniowanych przez użytkownika, U. Ten zestaw składa się z następujących elementów:
    • W kontekście oceny unchecked wszystkie zwykłe deklaracje operator op w T0.
    • W kontekście oceny checked wszystkie regularne i sprawdzone deklaracje operator op w T0 z wyjątkiem regularnych deklaracji, które mają zgodną deklarację pary checked operator.
  • Dla wszystkich deklaracji operator op w U i wszystkich zewnętrznych form tych operatorów, jeśli co najmniej jeden operator jest stosowalny (§12.4.6 - Odpowiedni członek funkcji) względem listy argumentów A, zestaw kandydatów na operatorów składa się ze wszystkich takich operatorów stosowalnych w T0.
  • W przeciwnym razie, jeśli T0 jest object, zestaw operatorów kandydatów jest pusty.
  • W przeciwnym razie zestaw operatorów kandydatów udostępnianych przez T0 jest zestawem operatorów kandydatów dostarczonych przez bezpośrednią klasę bazową T0lub efektywną klasę bazową T0, jeśli T0 jest parametrem typu.

Podobne reguły zostaną zastosowane podczas określania zestawu kandydatów na operatorów w interfejsach https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.

Sekcja §12.8.20 zostanie skorygowana, aby odzwierciedlić wpływ, jaki zaznaczone/niezaznaczone konteksty mają na rozstrzyganie przeciążenia operatorów jednoargumentowych i binarnych.

Przykład 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);
}

Przykład 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
{
}

Przykład 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
{
}

Operatory konwersji

checked operators konwersja jest zgodna z zasadami §15.10.4.

Jednak deklaracja checked operator wymaga pary deklaracji regular operator. W przeciwnym razie występuje błąd czasu kompilacji.

Poniższy akapit

Podpis operatora konwersji składa się z typu źródłowego i typu docelowego. To jest jedyna forma elementu członkowskiego, dla którego zwracany typ uczestniczy w sygnaturze. Niejawna lub jawna klasyfikacja operatora konwersji nie jest częścią sygnatury operatora. W związku z tym klasa lub struktura nie może zadeklarować zarówno niejawnego, jak i jawnego operatora konwersji z tymi samymi typami źródłowymi i docelowymi.

zostanie dostosowane tak, aby umożliwić typowi deklarowanie sprawdzanych i regularnych form jawnych konwersji z tymi samymi typami źródłowymi i docelowymi. Typ nie będzie mógł zadeklarować zarówno niejawnego, jak i sprawdzonego jawnego operatora konwersji z tymi samymi typami źródłowymi i docelowymi.

Przetwarzanie jawnych konwersji zdefiniowanych przez użytkownika

Trzeci punkt w §10.5.5:

  • Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i podniesionych, U. Ten zestaw składa się z operatorów konwersji zdefiniowanych przez użytkownika i podniesionych, niejawnych lub jawnych, które są zadeklarowane przez klasy lub struktury w D. Operatorzy ci konwertują z typu zawierającego lub zawartego w S do typu zawierającego lub zawartego w T. Jeśli U jest pusta, konwersja jest niezdefiniowana i wystąpi błąd czasu kompilacji.

zostanie zastąpiony następującymi punktami punktorowymi:

  • Znajdź zestaw operatorów konwersji, U0. Ten zestaw składa się z następujących elementów:
    • W kontekście ewaluacji unchecked operatory konwersji zdefiniowane przez użytkownika, niejawne lub zwykłe jawne, zadeklarowane przez klasy lub struktury w D.
    • W kontekście oceny checked zdefiniowane przez użytkownika niejawne lub zwykłe/sprawdzone jawne operatory konwersji zadeklarowane przez klasy lub struktury w D z wyjątkiem zwykłych jawnych operatorów konwersji, które mają zgodne pary deklaracji checked operator w ramach tego samego typu deklarującego.
  • Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i podniesionych, U. Ten zestaw składa się z zdefiniowanych przez użytkownika i podniesionych niejawnych lub jawnych operatorów konwersji w U0, które konwertują z typu obejmującego lub obejmowanego przez S do typu obejmującego lub obejmowanego przez T. Jeśli U jest pusta, konwersja jest niezdefiniowana i wystąpi błąd czasu kompilacji.

Sekcja o operatorach Checked i Unchecked §11.8.20 zostanie dostosowana, aby odzwierciedlić wpływ, jaki kontekst checked/unchecked ma na przetwarzanie jawnych konwersji zdefiniowanych przez użytkownika.

Wdrażanie operatorów

checked operator nie implementuje regular operator i odwrotnie.

Drzewa wyrażeń Linq

Checked operators będzie obsługiwane w drzewach wyrażeń LINQ. Zostanie utworzony węzeł UnaryExpression/BinaryExpression z odpowiednim MethodInfo. Zostaną użyte następujące metody fabryki:

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

Należy pamiętać, że język C# nie obsługuje przypisań w drzewach wyrażeń, dlatego sprawdzona inkrementacja/dekrementacja również nie będzie obsługiwana.

Nie ma metody fabrycznej dla dzielenia z kontrolą. Istnieje otwarte pytanie dotyczące tego — Sprawdzony podział w drzewach wyrażeń Linq.

Dynamiczny

Zbadamy koszt dodania wsparcia dla sprawdzanych operatorów przy dynamicznym wywołaniu w CoreCLR i będziemy dążyć do realizacji implementacji, jeśli koszt nie będzie zbyt wysoki. Jest to cytat z https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md.

Wady i mankamenty

Zwiększa to dodatkową złożoność języka i umożliwia użytkownikom wprowadzanie większej liczby zmian powodujących niezgodność w ich typach.

Alternatywy

Ogólne interfejsy matematyczne, które biblioteki planują uwidocznić, mogą uwidocznić nazwane metody (takie jak AddChecked). Podstawową wadą jest to, że jest to mniej czytelne/konserwowalne i nie uzyskuje korzyści z reguł pierwszeństwa języka wokół operatorów.

W tej sekcji wymieniono omówione alternatywy, ale nie zaimplementowano

Umieszczanie słowa kluczowego checked

Alternatywnie słowo kluczowe checked można przenieść do miejsca bezpośrednio przed słowem kluczowym 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) {...}

Można go również przenieść do zestawu modyfikatorów operatorów:

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

słowo kluczowe unchecked

Pojawiły się sugestie dotyczące obsługi słowa kluczowego unchecked w tym samym położeniu co słowo kluczowe checked z następującymi możliwymi znaczeniami:

  • W celu wyraźnego odzwierciedlenia regularnego charakteru operatora, lub...
  • Być może wyznaczyć specjalny rodzaj operatora, który powinien być używany w kontekście unchecked. Język może obsługiwać op_Addition, op_CheckedAdditioni op_UncheckedAddition, aby ograniczyć liczbę zmian powodujących niekompatybilność. Spowoduje to dodanie kolejnej warstwy złożoności, która prawdopodobnie nie jest konieczna w większości kodu.

Nazwy operatorów w ECMA-335

Alternatywnie nazwy operatorów mogą być op_UnaryNegationChecked, op_AdditionChecked, op_SubtractionChecked, op_MultiplyChecked, op_DivisionChecked, z Zaznaczone na końcu. Wygląda jednak na to, że istnieje już ustanowiony wzorzec, aby zakończyć nazwy słowem 'operator'. Na przykład istnieje operator op_UnsignedRightShift, a nie operator op_RightShiftUnsigned.

Checked operators nie można zastosować w kontekście unchecked

Kompilator, kiedy przeszukuje członków w celu odnalezienia potencjalnych operatorów zdefiniowanych przez użytkownika w kontekście unchecked, może zignorować checked operators. Jeśli napotkano metadane, które definiują tylko checked operator, wystąpi błąd kompilacji.

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

Bardziej skomplikowane reguły wyszukiwania operatorów i rozpoznawania przeciążenia w kontekście checked

Kompilator podczas przeszukiwania członków w celu znalezienia kandydatów na operatory zdefiniowane przez użytkownika w kontekście checked uwzględnia również odpowiednie operatory kończące się Checked. Oznacza to, że jeśli kompilator próbował znaleźć odpowiednie elementy członkowskie funkcji dla operatora dodawania binarnego, szukałby zarówno op_Addition, jak i op_AdditionChecked. Jeśli jedynym stosownym członem funkcji jest checked operator, zostanie on użyty. Jeśli istnieje zarówno regular operator, jak i checked operator i mają takie samo zastosowanie, preferowane będą checked operator. Jeśli zarówno regular operator, jak i checked operator istnieją, ale regular operator jest dokładnym dopasowaniem, podczas gdy checked operator nie, kompilator preferuje 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);
}

Innym sposobem, dzięki któremu można utworzyć zestaw kandydatów na operatorów zdefiniowanych przez użytkownika

Rozpoznawanie przeciążenia operatora jednoargumentowego

Zakładając, że regular operator pasuje do kontekstu oceny unchecked, checked operator pasuje do kontekstu oceny checked i operator, który nie ma formy checked (na przykład +), pasuje do dowolnego z kontekstów, pierwszy punktor w §12.4.4 — rozpoznawanie przeciążenia operatora jednoargumentowego:

zostanie zastąpiony następującymi dwoma punktami wypunktowania:

  • Zestaw operatorów zdefiniowanych przez użytkownika, dostarczonych przez X dla operacji operator op(x), pasujących do bieżącego sprawdzonego/niezaznaczonego kontekstu, jest ustalany na podstawie reguł kandydackich operatorów zdefiniowanych przez użytkownika .
  • Jeśli zestaw kandydatów operatorów zdefiniowanych przez użytkownika nie jest pusty, staje się to zestawem operatorów kandydatów dla operacji. W przeciwnym razie zestaw operatorów zdefiniowanych przez użytkownika określonych przez X dla operacji operator op(x)pasujących do odwrotnego zaznaczonego/niezaznaczonego kontekstu jest określany przy użyciu reguł §12.4.6 — Kandydujące operatory zdefiniowane przez użytkownika.

Rozpoznawanie przeciążenia operatora binarnego

Zakładając, że regular operator pasuje do kontekstu oceny unchecked, checked operator pasuje do kontekstu oceny checked i operator, który nie ma formy checked (na przykład %) pasuje do któregoś z kontekstów, pierwszy punkt w §12.4.5 – rozstrzyganie przeciążenia operatora binarnego:

  • Określa się zestaw operatorów zdefiniowanych przez użytkownika, udostępnianych przez X i Y dla operacji operator op(x,y). Zestaw składa się z unii kandydatów na operatorów dostarczonych przez X i kandydatów na operatorów dostarczonych przez Y, z których każdy jest określany przy użyciu reguł §12.4.6 — Kandydaci operatorów zdefiniowanych przez użytkownika. Jeśli X i Y są tego samego typu lub jeśli X i Y pochodzą ze wspólnego typu podstawowego, współużytkowane operatory kandydatów występują tylko raz w zestawie połączonym.

zostanie zastąpiony następującymi dwoma punktami wypunktowania:

  • Określa się zestaw operatorów zdefiniowanych przez użytkownika X i Y dla operacji operator op(x,y)dopasowanych do bieżącego zaznaczonego/niezaznaczonego kontekstu. Zestaw składa się z unii kandydatów na operatorów dostarczonych przez X i kandydatów na operatorów dostarczonych przez Y, z których każdy jest określany przy użyciu reguł §12.4.6 — Kandydaci operatorów zdefiniowanych przez użytkownika. Jeśli X i Y są tego samego typu lub jeśli X i Y pochodzą ze wspólnego typu podstawowego, współużytkowane operatory kandydatów występują tylko raz w zestawie połączonym.
  • Jeśli zestaw kandydatów operatorów zdefiniowanych przez użytkownika nie jest pusty, staje się to zestawem operatorów kandydatów dla operacji. W przeciwnym razie określa się zestaw kandydatów na operatorów zdefiniowanych przez użytkownika dostarczonych przez X i Y dla operacji operator op(x,y), odpowiadającej odwrotnemu kontekstowi zaznaczonego/niezaznaczonego. Zestaw składa się z unii kandydatów na operatorów dostarczonych przez X i kandydatów na operatorów dostarczonych przez Y, z których każdy jest określany przy użyciu reguł §12.4.6 — Kandydaci operatorów zdefiniowanych przez użytkownika. Jeśli X i Y są tego samego typu lub jeśli X i Y pochodzą ze wspólnego typu podstawowego, współużytkowane operatory kandydatów występują tylko raz w zestawie połączonym.
Przykład 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);
}
Przykład 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
{
}
Przykład 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
{
}
Przykład 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();
}
Przykład 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();
}

Przetwarzanie jawnych konwersji zdefiniowanych przez użytkownika

Zakładając, że regular operator odpowiada kontekstowi oceny unchecked i checked operator odpowiada kontekstowi oceny checked, trzeci punkt w §10.5.3 Ocena konwersji zdefiniowanych przez użytkownika:

  • Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i podniesionych, U. Ten zestaw składa się z operatorów konwersji zdefiniowanych przez użytkownika i podniesionych, niejawnych lub jawnych, które są zadeklarowane przez klasy lub struktury w D. Operatorzy ci konwertują z typu zawierającego lub zawartego w S do typu zawierającego lub zawartego w T. Jeśli U jest pusta, konwersja jest niezdefiniowana i wystąpi błąd czasu kompilacji.

zostanie zastąpiony następującymi punktami punktorowymi:

  • Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i podniesionych jawnie pasujących do bieżącego zaznaczonego/niezaznaczonego kontekstu, U0. Ten zestaw składa się z zdefiniowanych przez użytkownika i podniesionych jawnych operatorów konwersji zadeklarowanych przez klasy lub struktury w D, które odpowiadają bieżącemu zaznaczonemu/niezaznaczonemu kontekstowi i konwertują z typu obejmującego lub objętego S do typu obejmującego lub objętego przez T.
  • Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i podniesionych jawnie pasujących do odwrotnego zaznaczonego/niezaznaczonego kontekstu, U1. Jeśli U0 nie jest pusta, U1 jest pusta. W przeciwnym razie ten zestaw składa się z operatorów konwersji zdefiniowanych przez użytkownika i wywołanych jawnie, zadeklarowanych przez klasy lub struktury w D, które odpowiadają przeciwnemu kontekstowi sprawdzanemu/niesprawdzanemu i konwertują z typu, który obejmuje lub jest objęty przez S, do typu, który obejmuje lub jest objęty przez T.
  • Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i podniesionych, U. Ten zestaw składa się z operatorów z U0, U1oraz zdefiniowanych przez użytkownika i podniesionych niejawnych operatorów konwersji zadeklarowanych przez klasy lub struktury w D, które konwertują z typu obejmującego lub zawartego w S do typu obejmującego lub zawartego w T. Jeśli U jest pusta, konwersja jest niezdefiniowana i wystąpi błąd czasu kompilacji.

Jeszcze jeden kolejny sposób tworzenia zestawu operatorów zdefiniowanych przez użytkownika

Rozpoznawanie przeciążenia operatora jednoargumentowego

Pierwszy punkt w rozdziale §12.4.4 zostanie dostosowany następująco (dodatki są pogrubione).

  • Zestaw kandydatów na operatory zdefiniowane przez użytkownika udostępniane przez X dla operacji operator op(x) jest określany przy użyciu reguł sekcji "Kandydaci operatorów zdefiniowanych przez użytkownika" poniżej. Jeśli zestaw zawiera co najmniej jeden operator w formularzu zaznaczonym, wszystkie operatory w postaci regularnej są usuwane z zestawu.

Sekcja §12.8.20 zostanie skorygowana, aby odzwierciedlić wpływ, jaki kontekst sprawdzony/niesprawdzony ma na rozwiązywanie przeciążeń operatora jednoargumentowego.

Rozpoznawanie przeciążenia operatora binarnego

Pierwszy punktor w sekcji §12.4.5 zostanie dostosowany w następujący sposób (dodatki są pogrubione).

  • Określa się zestaw operatorów zdefiniowanych przez użytkownika, udostępnianych przez X i Y dla operacji operator op(x,y). Zestaw składa się z unii operatorów kandydatów dostarczonych przez X i operatorów kandydatów dostarczonych przez Y, z których każdy jest określany przy użyciu reguł "Operatory zdefiniowane przez użytkownika kandydata" poniżej. Jeśli X i Y są tego samego typu lub jeśli X i Y pochodzą ze wspólnego typu podstawowego, współużytkowane operatory kandydatów występują tylko raz w zestawie połączonym. Jeśli zestaw zawiera co najmniej jeden operator w formularzu zaznaczonym, wszystkie operatory w postaci regularnej są usuwane z zestawu.

Zaznaczone i niezaznaczone operatory w sekcji §12.8.20 zostaną dostosowane, aby odzwierciedlić wpływ, jaki kontekst sprawdzany/niesprawdzany ma na rozwiązanie przeciążenia operatorów binarnych.

Operatory kandydujące zdefiniowane przez użytkownika

Sekcja §12.4.6 — Operatory zdefiniowane przez użytkownika zostaną dostosowane w następujący sposób (dodatki są pogrubione).

Biorąc pod uwagę typ T i operację operator op(A), gdzie op jest operatorem przeciążalnym, a A jest listą argumentów, zestaw operatorów zdefiniowanych przez użytkownika dostarczonych przez T dla operator op(A) jest określany w następujący sposób:

  • Określ typ T0. Jeśli T jest typem dopuszczającym wartości null, T0 jest jego typem podstawowym, w przeciwnym razie T0 jest równy T.
  • Dla wszystkich deklaracji operator opw formie sprawdzonej i regularnej w kontekście oceny checked i tylko w ich regularnych formach w kontekście oceny unchecked w T0 oraz wszystkich podniesionych form takich operatorów, jeśli przynajmniej jeden operator pasuje (§12.6.4.2) w odniesieniu do listy argumentów A, to zestaw operatorów kandydujących składa się ze wszystkich takich pasujących operatorów w T0.
  • W przeciwnym razie, jeśli T0 jest object, zestaw operatorów kandydatów jest pusty.
  • W przeciwnym razie zestaw operatorów kandydatów udostępnianych przez T0 jest zestawem operatorów kandydatów dostarczonych przez bezpośrednią klasę bazową T0lub efektywną klasę bazową T0, jeśli T0 jest parametrem typu.

Podobne filtrowanie zostanie zastosowane podczas określania zestawu kandydatów na operatorów w interfejsach https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.

Sekcja §12.8.20 zostanie skorygowana, aby odzwierciedlić wpływ, jaki kontekst zaznaczony/niezaznaczony ma na rozstrzyganie przeciążeń operatorów jednoargumentowych i binarnych.

Przykład 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);
}
Przykład 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
{
}
Przykład 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
{
}
Przykład 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();
}
Przykład 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();
}

Przetwarzanie jawnych konwersji zdefiniowanych przez użytkownika

Trzeci punkt w §10.5.5:

  • Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i podniesionych, U. Ten zestaw składa się z operatorów konwersji zdefiniowanych przez użytkownika i podniesionych, niejawnych lub jawnych, które są zadeklarowane przez klasy lub struktury w D. Operatorzy ci konwertują z typu zawierającego lub zawartego w S do typu zawierającego lub zawartego w T. Jeśli U jest pusta, konwersja jest niezdefiniowana i wystąpi błąd czasu kompilacji.

zostanie zastąpiony następującymi punktami punktorowymi:

  • Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i jawnie podniesionych, U0. Ten zestaw składa się z operatorów jawnych konwersji zdefiniowanych przez użytkownika i podniesionych, zadeklarowanych przez klasy lub struktury w D, w ich zbadanej i regularnej postaci w kontekście oceny checked i tylko w tej regularnej formie w kontekście oceny unchecked, i konwersji z typu obejmującego lub obejmowanego przez S na typ obejmujący lub obejmowany przez T.
  • Jeśli U0 zawiera co najmniej jeden operator w formularzu zaznaczonym, wszystkie operatory w postaci regularnej zostaną usunięte z zestawu.
  • Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i podniesionych, U. Ten zestaw składa się z operatorów z U0oraz zdefiniowanych przez użytkownika i uniesionych niejawnych operatorów konwersji, zadeklarowanych przez klasy lub struktury w D, które konwertują z typu obejmującego lub objętego przez S do typu obejmującego lub objętego przez T. Jeśli U jest pusta, konwersja jest niezdefiniowana i wystąpi błąd czasu kompilacji.

Sekcja operatorów zaznaczonych i niezaznaczonych §12.8.20 zostanie poprawiona, aby odzwierciedlić wpływ kontekstu zaznaczonego/niezaznaczonego na przetwarzanie jawnych konwersji definiowanych przez użytkownika.

Zaznaczone a niezaznaczone konteksty w checked operator

Kompilator może traktować domyślny kontekst checked operator jako sprawdzony. Deweloper musi jawnie użyć unchecked, jeśli część algorytmu nie powinna uczestniczyć w checked context. Jednak może to nie zadziałać w przyszłości, jeśli zaczniemy zezwalać checked/unchecked tokeny jako modyfikatory na operatorach w celu ustawienia kontekstu w treści. Modyfikator i słowo kluczowe mogą być ze sobą sprzeczne. Ponadto nie moglibyśmy zrobić tego samego (traktować kontekstu domyślnego jako niezaznaczonego) dla regular operator, ponieważ byłaby to zmiana naruszająca zgodność.

Nierozwiązane pytania

Czy język powinien zezwalać na modyfikatory checked i unchecked metod (np. static checked void M())? Umożliwiłoby to usunięcie poziomów zagnieżdżania dla metod, które tego wymagają.

Dzielenie kontrolowane w drzewach wyrażeń LINQ

Nie ma metody fabrycznej do utworzenia kontrolowanego węzła dzielenia i nie ma członka ExpressionType.DivideChecked. Nadal można użyć następującej metody fabrycznej, aby utworzyć standardowy węzeł podziału, w którym MethodInfo wskazuje na metodę op_CheckedDivision. Konsumenci będą musieli sprawdzić nazwę, aby wywnioskować kontekst.

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

Należy pamiętać, że mimo że §12.8.20 sekcja wyświetla listę operatorów / jako jeden z operatorów, których dotyczy zaznaczony/niezaznawany kontekst oceny, IL nie ma specjalnego kodu operacji do wykonania sprawdzanego podziału. Kompilator obecnie zawsze używa metody fabrycznej, niezależnie od kontekstu.

Propozycja: Sprawdzony użytkownikowo zdefiniowany podział nie będzie obsługiwany w drzewach wyrażeń LINQ.

(Rozwiązano) Czy powinniśmy obsługiwać niejawne sprawdzone operatory konwersji?

Ogólnie rzecz biorąc, niejawne operatory konwersji nie powinny zgłaszać wyjątków.

Propozycja : nr.

Rozwiązanie : zatwierdzone - - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md#checked-implicit-conversions

Spotkania projektowe

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