Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
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 .
Problem z mistrzem: https://github.com/dotnet/csharplang/issues/4665
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.
- Operatory jednoargumentowe
++i--§12.8.16 i §12.9.6. - Operator jednoargumentowy
-§12.9.3. - Operatory binarne
+,-,*i/§12.10. - Jawne operatory konwersji.
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śliTjest typem dopuszczającym wartości null,T0jest jego typem podstawowym, w przeciwnym razieT0jest równyT. - Znajdź zestaw operatorów zdefiniowanych przez użytkownika,
U. Ten zestaw składa się z następujących elementów:-
W kontekście oceny
uncheckedwszystkie zwykłe deklaracjeoperator opwT0. -
W kontekście oceny
checkedwszystkie regularne i sprawdzone deklaracjeoperator opwT0z wyjątkiem regularnych deklaracji, które mają zgodną deklarację parychecked operator.
-
W kontekście oceny
- Dla wszystkich deklaracji
operator opwUi 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ówA, zestaw kandydatów na operatorów składa się ze wszystkich takich operatorów stosowalnych wT0. - W przeciwnym razie, jeśli
T0jestobject, zestaw operatorów kandydatów jest pusty. - W przeciwnym razie zestaw operatorów kandydatów udostępnianych przez
T0jest zestawem operatorów kandydatów dostarczonych przez bezpośrednią klasę bazowąT0lub efektywną klasę bazowąT0, jeśliT0jest 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 wD. Operatorzy ci konwertują z typu zawierającego lub zawartego wSdo typu zawierającego lub zawartego wT. JeśliUjest 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
uncheckedoperatory konwersji zdefiniowane przez użytkownika, niejawne lub zwykłe jawne, zadeklarowane przez klasy lub struktury wD. -
W kontekście oceny
checkedzdefiniowane przez użytkownika niejawne lub zwykłe/sprawdzone jawne operatory konwersji zadeklarowane przez klasy lub struktury wDz wyjątkiem zwykłych jawnych operatorów konwersji, które mają zgodne pary deklaracjichecked operatorw ramach tego samego typu deklarującego.
-
W kontekście ewaluacji
- 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 wU0, które konwertują z typu obejmującego lub obejmowanego przezSdo typu obejmującego lub obejmowanego przezT. JeśliUjest 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_CheckedAdditioniop_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:
- Zestaw operatorów zdefiniowanych przez użytkownika dostarczonych przez
Xdla operacjioperator op(x)ustala się zgodnie z regułami §12.4.6 — Kandydujące operatory zdefiniowane przez użytkownika.
zostanie zastąpiony następującymi dwoma punktami wypunktowania:
- Zestaw operatorów zdefiniowanych przez użytkownika, dostarczonych przez
Xdla operacjioperator 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
Xdla operacjioperator 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
XiYdla operacjioperator op(x,y). Zestaw składa się z unii kandydatów na operatorów dostarczonych przezXi kandydatów na operatorów dostarczonych przezY, z których każdy jest określany przy użyciu reguł §12.4.6 — Kandydaci operatorów zdefiniowanych przez użytkownika. JeśliXiYsą tego samego typu lub jeśliXiYpochodzą 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
XiYdla operacjioperator op(x,y)dopasowanych do bieżącego zaznaczonego/niezaznaczonego kontekstu. Zestaw składa się z unii kandydatów na operatorów dostarczonych przezXi kandydatów na operatorów dostarczonych przezY, z których każdy jest określany przy użyciu reguł §12.4.6 — Kandydaci operatorów zdefiniowanych przez użytkownika. JeśliXiYsą tego samego typu lub jeśliXiYpochodzą 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
XiYdla operacjioperator op(x,y), odpowiadającej odwrotnemu kontekstowi zaznaczonego/niezaznaczonego. Zestaw składa się z unii kandydatów na operatorów dostarczonych przezXi kandydatów na operatorów dostarczonych przezY, z których każdy jest określany przy użyciu reguł §12.4.6 — Kandydaci operatorów zdefiniowanych przez użytkownika. JeśliXiYsą tego samego typu lub jeśliXiYpochodzą 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 wD. Operatorzy ci konwertują z typu zawierającego lub zawartego wSdo typu zawierającego lub zawartego wT. JeśliUjest 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 wD, które odpowiadają bieżącemu zaznaczonemu/niezaznaczonemu kontekstowi i konwertują z typu obejmującego lub objętegoSdo typu obejmującego lub objętego przezT. - Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i podniesionych jawnie pasujących do odwrotnego zaznaczonego/niezaznaczonego kontekstu,
U1. JeśliU0nie jest pusta,U1jest 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 wD, które odpowiadają przeciwnemu kontekstowi sprawdzanemu/niesprawdzanemu i konwertują z typu, który obejmuje lub jest objęty przezS, do typu, który obejmuje lub jest objęty przezT. - Znajdź zestaw odpowiednich operatorów konwersji zdefiniowanych przez użytkownika i podniesionych,
U. Ten zestaw składa się z operatorów zU0,U1oraz zdefiniowanych przez użytkownika i podniesionych niejawnych operatorów konwersji zadeklarowanych przez klasy lub struktury wD, które konwertują z typu obejmującego lub zawartego wSdo typu obejmującego lub zawartego wT. JeśliUjest 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
Xdla operacjioperator 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
XiYdla operacjioperator op(x,y). Zestaw składa się z unii operatorów kandydatów dostarczonych przezXi operatorów kandydatów dostarczonych przezY, z których każdy jest określany przy użyciu reguł "Operatory zdefiniowane przez użytkownika kandydata" poniżej. JeśliXiYsą tego samego typu lub jeśliXiYpochodzą 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śliTjest typem dopuszczającym wartości null,T0jest jego typem podstawowym, w przeciwnym razieT0jest równyT. - Dla wszystkich deklaracji
operator opw formie sprawdzonej i regularnej w kontekście ocenycheckedi tylko w ich regularnych formach w kontekście ocenyuncheckedwT0oraz wszystkich podniesionych form takich operatorów, jeśli przynajmniej jeden operator pasuje (§12.6.4.2) w odniesieniu do listy argumentówA, to zestaw operatorów kandydujących składa się ze wszystkich takich pasujących operatorów wT0. - W przeciwnym razie, jeśli
T0jestobject, zestaw operatorów kandydatów jest pusty. - W przeciwnym razie zestaw operatorów kandydatów udostępnianych przez
T0jest zestawem operatorów kandydatów dostarczonych przez bezpośrednią klasę bazowąT0lub efektywną klasę bazowąT0, jeśliT0jest 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 wD. Operatorzy ci konwertują z typu zawierającego lub zawartego wSdo typu zawierającego lub zawartego wT. JeśliUjest 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 wD, w ich zbadanej i regularnej postaci w kontekście ocenycheckedi tylko w tej regularnej formie w kontekście ocenyunchecked, i konwersji z typu obejmującego lub obejmowanego przezSna typ obejmujący lub obejmowany przezT. - Jeśli
U0zawiera 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 zU0oraz zdefiniowanych przez użytkownika i uniesionych niejawnych operatorów konwersji, zadeklarowanych przez klasy lub struktury wD, które konwertują z typu obejmującego lub objętego przezSdo typu obejmującego lub objętego przezT. JeśliUjest 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
C# feature specifications