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.
- 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śliT
jest typem dopuszczającym wartości null,T0
jest jego typem podstawowym, w przeciwnym razieT0
jest 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
unchecked
wszystkie zwykłe deklaracjeoperator op
wT0
. -
W kontekście oceny
checked
wszystkie regularne i sprawdzone deklaracjeoperator op
wT0
z wyjątkiem regularnych deklaracji, które mają zgodną deklarację parychecked operator
.
-
W kontekście oceny
- Dla wszystkich deklaracji
operator op
wU
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ówA
, zestaw kandydatów na operatorów składa się ze wszystkich takich operatorów stosowalnych wT0
. - W przeciwnym razie, jeśli
T0
jestobject
, 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ąT0
lub efektywną klasę bazowąT0
, jeśliT0
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 wD
. Operatorzy ci konwertują z typu zawierającego lub zawartego wS
do typu zawierającego lub zawartego wT
. JeśliU
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 wD
. -
W kontekście oceny
checked
zdefiniowane przez użytkownika niejawne lub zwykłe/sprawdzone jawne operatory konwersji zadeklarowane przez klasy lub struktury wD
z wyjątkiem zwykłych jawnych operatorów konwersji, które mają zgodne pary deklaracjichecked operator
w 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 przezS
do typu obejmującego lub obejmowanego przezT
. JeśliU
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_CheckedAddition
iop_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
X
dla 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
X
dla 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
X
dla 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
X
iY
dla operacjioperator op(x,y)
. Zestaw składa się z unii kandydatów na operatorów dostarczonych przezX
i 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śliX
iY
są tego samego typu lub jeśliX
iY
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
iY
dla operacjioperator op(x,y)
dopasowanych do bieżącego zaznaczonego/niezaznaczonego kontekstu. Zestaw składa się z unii kandydatów na operatorów dostarczonych przezX
i 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śliX
iY
są tego samego typu lub jeśliX
iY
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
iY
dla operacjioperator op(x,y)
, odpowiadającej odwrotnemu kontekstowi zaznaczonego/niezaznaczonego. Zestaw składa się z unii kandydatów na operatorów dostarczonych przezX
i 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śliX
iY
są tego samego typu lub jeśliX
iY
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 wD
. Operatorzy ci konwertują z typu zawierającego lub zawartego wS
do typu zawierającego lub zawartego wT
. JeśliU
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 wD
, które odpowiadają bieżącemu zaznaczonemu/niezaznaczonemu kontekstowi i konwertują z typu obejmującego lub objętegoS
do 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śliU0
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 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
,U1
oraz 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 wS
do typu obejmującego lub zawartego wT
. JeśliU
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 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
X
iY
dla operacjioperator op(x,y)
. Zestaw składa się z unii operatorów kandydatów dostarczonych przezX
i 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śliX
iY
są tego samego typu lub jeśliX
iY
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śliT
jest typem dopuszczającym wartości null,T0
jest jego typem podstawowym, w przeciwnym razieT0
jest równyT
. - Dla wszystkich deklaracji
operator op
w formie sprawdzonej i regularnej w kontekście ocenychecked
i tylko w ich regularnych formach w kontekście ocenyunchecked
wT0
oraz 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
T0
jestobject
, 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ąT0
lub efektywną klasę bazowąT0
, jeśliT0
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 wD
. Operatorzy ci konwertują z typu zawierającego lub zawartego wS
do typu zawierającego lub zawartego wT
. JeśliU
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 wD
, w ich zbadanej i regularnej postaci w kontekście ocenychecked
i tylko w tej regularnej formie w kontekście ocenyunchecked
, i konwersji z typu obejmującego lub obejmowanego przezS
na typ obejmujący lub obejmowany przezT
. - 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 zU0
oraz 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 przezS
do typu obejmującego lub objętego przezT
. JeśliU
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
C# feature specifications