Проверенные пользовательские операторы
Заметка
Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Она включает предлагаемые изменения спецификации, а также информацию, необходимую во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.
Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Различия зафиксированы в соответствующих заметках собраний по проектированию языка (LDM).
Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .
Сводка
C# должен поддерживать определение checked
вариантов следующих определяемых пользователем операторов, чтобы пользователи могли как включать, так и отключать поведение переполнения в зависимости от необходимости.
- Унарные операторы
++
и--
§12.8.16 и §12.9.6. - Унарный оператор
-
§12.9.3. - Двоичные операторы
+
,-
,*
и/
§12.10. - Явные операторы преобразования.
Мотивация
Пользователь не может объявить тип и поддерживать как проверенные, так и непроверенные версии оператора. Это затрудняет перенос различных алгоритмов для использования предлагаемых интерфейсов generic math
, предоставляемых командой библиотек. Аналогичным образом это делает невозможным предоставление такого типа, как Int128
или UInt128
без одновременного предоставления языком собственной поддержки, чтобы избежать разрушающих изменений.
Подробный дизайн
Синтаксис
Грамматика операторов (§15.10) будет изменена, чтобы разрешить использование ключевого слова checked
после ключевого слова operator
непосредственно перед токеном оператора.
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 ')'
;
Например:
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) {...}
Для краткости ниже оператор с ключевым словом checked
называется checked operator
и оператором без него называется regular operator
. Эти термины не применимы к операторам, у которых нет формы checked
.
Семантика
Как ожидается, определяемая пользователем checked operator
создает исключение, если результат операции слишком велик, чтобы его можно было представить в типе назначения. Что значит быть слишком большим в действительности зависит от характера целевого типа и не предписывается языком. Как правило, исключение вызывается System.OverflowException
, но язык не имеет конкретных требований в отношении этого.
Ожидается, что определяемая пользователем regular operator
не создает исключение, если результат операции слишком велик, чтобы представить в целевом типе. Вместо этого ожидается, что будет возвращен экземпляр, представляющий усеченный результат. Что значит быть слишком большим и подвергнуться усечению, на самом деле зависит от природы целевого типа и не предписывается языком.
Все существующие пользовательские операторы, имеющие форму checked
и поддерживаемые в категории regular operators
. Понятно, что многие из них, скорее всего, не следуют семантике, указанной выше, но для целей семантического анализа компилятор предполагает, что они являются.
Проверенный или непроверенный контекст в checked operator
Контекст, отмеченный как установленный или снятый в составе checked operator
, не зависит от наличия ключевого слова checked
. Другими словами, контекст такой же, как и сразу в начале объявления оператора. Разработчику потребуется явно переключить контекст, если часть алгоритма не может полагаться на контекст по умолчанию.
Имена в метаданных
Раздел I.10.3.1 "Унарные операторы" ECMA-335 будет изменен, чтобы включить op_CheckedIncrement, op_CheckedDecrement, op_CheckedUnaryNegation в качестве имен методов, реализующих проверенные ++
, --
и -
унарные операторы.
Раздел I.10.3.2 "Двоичные операторы" стандарта ECMA-335 будет изменен для включения op_CheckedAddition, op_CheckedSubtraction, op_CheckedMultiply, op_CheckedDivision в качестве имен методов, реализующих проверку двоичных операторов +
, -
, *
и /
.
Раздел "Операторы преобразования I.10.3.3" ECMA-335 будет изменен, чтобы включить op_CheckedExplicit в качестве имени метода, реализующего проверенный оператор явного преобразования.
Унарные операторы
Унарный checked operators
следует правилам §15.10.2.
Кроме того, для объявления checked operator
требуется парное объявление regular operator
(возвращаемый тип также должен совпадать). Ошибка во время компиляции возникает в противном случае.
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);
}
Двоичные операторы
Двоичным checked operators
следует соблюдать правила из §15.10.3.
Кроме того, для объявления checked operator
требуется парное объявление regular operator
(возвращаемый тип также должен совпадать). Ошибка во время компиляции возникает в противном случае.
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);
}
Возможные операторы, определяемые пользователем
Операторы пользователей-кандидатов (§12.4.6) будут изменены следующим образом (дополнения и изменения выделены полужирным шрифтом).
Учитывая тип T
и данную операцию operator op(A)
, где op
является перегруженным оператором и A
является списком аргументов, набор пользовательских операторов, предоставляемых T
для operator op(A)
, определяется следующим образом.
- Определите тип
T0
. ЕслиT
является nullable типом,T0
— его базовый тип, иначеT0
равноT
. - Найдите набор операторов, определённых пользователем
U
. Этот набор состоит из следующих элементов:-
В контексте оценки
unchecked
учитываются все обычные объявленияoperator op
вT0
. -
В контексте оценки
checked
все проверенные и обычные объявленияoperator op
вT0
, за исключением регулярных объявлений, которые имеют парное сопоставление сchecked operator
объявлениями.
-
В контексте оценки
- Для всех объявлений
operator op
вU
и всех обобщённых форм таких операторов, если хотя бы один оператор применим (§12.4.6 - Применимый член функции) относительно списка аргументовA
, то набор операторов-кандидатов состоит из всех таких применимых операторов вT0
. - В противном случае, если
T0
равенobject
, набор кандидатов на операторов пуст. - В противном случае набор операторов кандидатов, предоставляемых
T0
, является набором операторов кандидатов, предоставляемых прямым базовым классомT0
, или эффективным базовым классомT0
, еслиT0
является параметром типа.
Аналогичные правила будут применяться при определении набора операторов-кандидатов в интерфейсах https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfaces.
Раздел §12.8.20 будет изменен, чтобы отразить влияние установленного или снятого контекста на разрешение перегрузки унарных и бинарных операторов.
Пример #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);
}
Пример #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
{
}
Пример #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
{
}
Операторы преобразования
Преобразование checked operators
следуйте правилам §15.10.4.
Однако для объявления checked operator
требуется парное объявление regular operator
. Ошибка во время компиляции возникает в противном случае.
Следующий абзац
Подпись оператора преобразования состоит из исходного типа и целевого типа. (Это единственная форма элемента, для которого возвращаемый тип участвует в сигнатуре.) Неявная или явная классификация оператора преобразования не является частью подписи оператора. Таким образом, класс или структура не может объявлять как неявный, так и явный оператор преобразования с одинаковыми исходными и целевыми типами.
будет изменено, чтобы разрешить типу объявлять проверенные и регулярные формы явных преобразований с одинаковыми исходными и целевыми типами. Тип не может объявлять как неявный, так и проверенный явный оператор преобразования с одинаковыми исходными и целевыми типами.
Обработка специфически заданных пользователем явных преобразований
Третий маркер в §10.5.5:
- Найдите набор применимых пользовательских и поднятых операторов преобразования,
U
. Этот набор включает определяемые пользователем и обобщенные (или перегруженные) неявные или явные операторы преобразования, объявленные классами или структурами вD
, которые выполняют преобразование из типа, включающего или включенного вS
, в тип, включающий или включенный вT
. ЕслиU
пуст, преобразование не определено и возникает ошибка во время компиляции.
будут заменены следующими маркерами:
- Найдите набор операторов преобразования,
U0
. Этот набор состоит из следующих элементов:-
В контексте оценки
unchecked
определяемые пользователем неявные или обычные явные операторы преобразования, объявленные классами или структурами вD
. -
В контексте оценки
checked
определяемые пользователем неявные или регулярные или проверенные явные операторы преобразования, объявленные классами или структурами вD
, кроме обычных явных операторов преобразования, которые имеют парно-мудрое сопоставлениеchecked operator
объявление в том же декларативном типе.
-
В контексте оценки
- Найдите набор применимых пользовательских и поднятых операторов преобразования,
U
. Этот набор состоит из определяемых пользователем и снятых неявных или явных операторов преобразования вU0
, которые преобразуются из типа, охватывающего или охватываемогоS
в тип, охватывающий или охватываемыйT
. ЕслиU
пуст, преобразование не определено и возникает ошибка во время компиляции.
Проверенные и снятые операторы разделу "11.8.20" будут изменены, чтобы отразить влияние, которое отображается в контексте проверки или отмены проверки при обработке определяемых пользователем явных преобразований.
Внедрение операторов
checked operator
не реализует regular operator
и наоборот.
Деревья выражений Linq
Checked operators
будет поддерживаться в деревах выражений Linq. Узел UnaryExpression
/BinaryExpression
будет создан с соответствующим MethodInfo
.
Будут использоваться следующие методы фабрики:
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);
Обратите внимание, что C# не поддерживает присваивания в деревьях выражений, поэтому проверки инкремента и декремента также не поддерживаются.
Не существует метода фабрики для проверенного деления. Существует открытый вопрос об этом - проверенное деление в деревах выражений Linq.
Динамический
Мы изучим затраты на добавление поддержки проверяемых операторов в динамическом вызове в CoreCLR и внедрим её, если затраты не будут слишком высокими. Это цитата из https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-09.md.
Недостатки
Это увеличивает сложность языка и позволяет пользователям вносить больше видов разрушительных изменений в их типы.
Альтернативы
Универсальные математические интерфейсы, которые библиотеки планируют предоставлять, могут предоставлять именованные методы (например, AddChecked
). Основной недостаток заключается в том, что это менее читаемо и удобно для поддержки и не использует правила приоритета операторов в языке.
В этом разделе перечислены альтернативные варианты, но не реализованные
Размещение ключевого слова checked
Кроме того, ключевое слово checked
можно переместить в место прямо перед ключевым словом 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) {...}
Или его можно переместить в набор модификаторов операторов:
operator_modifier
: 'public'
| 'static'
| 'extern'
| 'checked'
| operator_modifier_unsafe
;
public static checked T operator ++(T x) {...}
public static checked T operator --(T x) {...}
public static checked T operator -(T x) {...}
public static checked T operator +(T lhs, T rhs) {...}
public static checked T operator -(T lhs, T rhs) {...}
public static checked T operator *(T lhs, T rhs) {...}
public static checked T operator /(T lhs, T rhs) {...}
public static checked explicit operator U(T x) {...}
public static checked T I1.operator ++(T x) {...}
public static checked T I1.operator --(T x) {...}
public static checked T I1.operator -(T x) {...}
public static checked T I1.operator +(T lhs, T rhs) {...}
public static checked T I1.operator -(T lhs, T rhs) {...}
public static checked T I1.operator *(T lhs, T rhs) {...}
public static checked T I1.operator /(T lhs, T rhs) {...}
public static checked explicit I1.operator U(T x) {...}
ключевое слово unchecked
Были предложения по поддержке ключевого слова unchecked
в той же позиции, что и ключевое слово checked
со следующими возможными значениями:
- Просто для явного отражения регулярного характера оператора или
- Возможно, для обозначения особенной специфики оператора, который должен использоваться в контексте
unchecked
. Язык может поддерживатьop_Addition
,op_CheckedAddition
иop_UncheckedAddition
, чтобы ограничить количество критических изменений. Это добавляет еще один уровень сложности, который, скорее всего, не требуется в большинстве кода.
Имена операторов в ECMA-335
Кроме того, имена операторов могут быть op_UnaryNegationChecked, op_AdditionChecked, op_SubtractionChecked, op_MultiplyChecked, op_DivisionChecked, с Checked в конце. Похоже, что уже установлен шаблон, когда имена заканчиваются словом "оператора". Например, существует оператор op_UnsignedRightShift, а не оператор op_RightShiftUnsigned.
Checked operators
неприменимо в контексте unchecked
Компилятор при выполнении поиска членов для нахождения пользовательских операторов-кандидатов в контексте unchecked
может игнорировать checked operators
. Если обнаружены метаданные, определяющие только checked operator
, возникнет ошибка компиляции.
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);
}
Более сложные правила поиска операторов и разрешения перегрузки в контексте checked
Компилятор, выполняя поиск элемента для нахождения кандидатов на пользовательские операторы в контексте checked
, также будет учитывать применимые операторы, заканчивающиеся на Checked
. То есть, если компилятор пытается найти применимые члены функции для оператора двоичного добавления, он будет искать как op_Addition
, так и op_AdditionChecked
. Если единственным применимым элементом функции является checked operator
, он будет использоваться. Если существуют regular operator
и checked operator
и применяются одинаково, то checked operator
будет предпочтительнее. Если и regular operator
, и checked operator
существуют, но regular operator
точно совпадает, а checked operator
нет, компилятор предпочитает 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);
}
Еще один способ создания набора пользовательских операторов
Разрешение перегрузки унарного оператора
Предположим, что regular operator
совпадают в контексте оценки unchecked
, checked operator
соответствует контексту оценки checked
и оператор, который не имеет формы checked
(например, +
), соответствует одному из контекстов, то первый пункт в §12.4.4 — разрешение перегрузки унарного оператора:
- Набор определяемых пользователем операторов, предоставляемых
X
для операцииoperator op(x)
, определяется с помощью правил §12.4.6 — операторов, определяемых пользователем,.
будет заменено следующими двумя точками маркера:
- Набор определяемых пользователем операторов, предоставляемых
для операции , определяется в соответствующем проверяемом или непроверяемом контексте с использованием правил определения кандидатов операторов, заданных пользователем. - Если набор пользовательских операторов не является пустым, это становится набором операторов-кандидатов для операции. В противном случае набор кандидатов пользовательских операторов, предоставляемого
X
для операцииoperator op(x)
, совпадающего с противоположным контекстом проверки или без проверки, определяется с помощью правил §12.4.6 — кандидаты пользовательских операторов.
Разрешение перегрузки двоичных операторов
Предположим, что regular operator
соответствует контексту оценки unchecked
, checked operator
соответствует контексту оценки checked
и оператор, который не имеет формы checked
(например, %
), соответствует любому из контекстов, то первый пункт в списке в §12.4.5 — разрешение перегрузки двоичных операторов:
- Определяется набор потенциальных пользовательских операторов, предоставляемый
X
иY
для операцииoperator op(x,y)
. Набор состоит из объединения кандидатных операторов, предоставляемыхX
, и кандидатных операторов, предоставляемыхY
, при этом каждый определяется согласно правилам §12.4.6 — кандидатные операторы, определяемые пользователем. ЕслиX
иY
одного типа, илиX
иY
являются производными от общего базового типа, то операторы, общие для кандидатов, встречаются в объединённом наборе только один раз.
будет заменено следующими двумя точками маркера:
- Определяется набор пользовательских определённых операторов, предоставляемых
X
иY
для операцииoperator op(x,y)
, соответствующего текущему проверенному/непроверенному контексту. Набор состоит из объединения операторов-кандидатов, предоставляемыхX
, и операторов-кандидатов, предоставляемыхY
, при этом каждый определяется с помощью правил §12.4.6 — кандидаты на пользовательские операторы. ЕслиX
иY
одного типа, или еслиX
иY
производные от общего базового типа, то общие операторы-кандидаты встречаются в объединенном наборе только один раз. - Если набор операторов, определяемых пользователем, не является пустым, то он становится набором операторов-кандидатов для операции. В противном случае определяется набор пользовательских операторов, предоставляемых
X
иY
для операцииoperator op(x,y)
, соответствующей противоположному контексту проверённости/непроверённости. Набор состоит из объединения кандидатных операторов, предоставляемыхX
, и кандидатных операторов, предоставляемыхY
, каждый из которых определяется согласно правилам §12.4.6 — Пользовательские определенные операторные кандидаты. ЕслиX
иY
одного типа, или еслиX
иY
производные от общего базового типа, то общие кандидаты-операторы встречаются только один раз в объединённом наборе.
Пример #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);
}
Пример #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
{
}
Пример #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
{
}
Пример #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();
}
Пример #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();
}
Обработка явных преобразований, заданных пользователем
Предположим, что regular operator
соответствует контексту оценки unchecked
и checked operator
соответствует контексту оценки checked
, третий пункт в §10.5.3 Оценка определяемых пользователем преобразований:
- Найдите набор применимых пользовательских и поднятых операторов преобразования,
U
. Этот набор состоит из определяемых пользователем и поднимаемых неявных или явных операторов преобразования, которые объявлены классами или структурами вD
и преобразовывают тип, включающий или охваченныйS
, в тип, включающий или охватываемыйT
. ЕслиU
пуст, преобразование не определено и возникает ошибка во время компиляции.
будет заменено следующими пунктами списка:
- Найдите набор применимых пользовательских и продвинутых явных операторов преобразования , соответствующих текущему проверенному/непроверенному контексту,
U0
. Этот набор состоит из операторов явного преобразования, определяемых пользователем и предоставляемых (или поднятых) классами или структурами вD
, которые соответствуют текущему проверенному/непроверенному контексту и преобразуют из типа, включающего или включаемогоS
в тип, включающий или включаемыйT
. - Найдите набор применимых пользовательских и поднятых явных операторов преобразования , соответствующих противоположному контексту проверки/непроверкии
U1
. ЕслиU0
не пуст,U1
пуст. В противном случае этот набор состоит из определенных пользователем и снятых явных операторов преобразования, объявленных классами или структурами вD
, которые соответствуют противоположному контексту "проверен/непроверен" и преобразуются из типа, охватывающего или охватываемогоS
, в тип, охватывающий или охватываемыйT
. - Найдите набор применимых пользовательских и поднятых операторов преобразования,
U
. Этот набор состоит из операторов изU0
,U1
, а также из определяемых пользователем и поднятых неявных операторов преобразования, объявленных классами или структурами вD
, которые преобразуют из типа, охватывающего или охватываемогоS
, в тип, охватывающий или охватываемыйT
. ЕслиU
пуст, преобразование не определено и возникает ошибка во время компиляции.
Еще один способ создания набора пользовательских операторов, определенных кандидатом
Разрешение перегрузки унарного оператора
Первый пункт в разделе §12.4.4 будет изменен следующим образом (дополнения выделены полужирным шрифтом).
- Набор определяемых пользователем операторов, предоставляемых
X
для операцииoperator op(x)
, определяется с помощью правил "Потенциальные определяемые пользователем операторы" ниже. Если набор содержит по крайней мере один оператор в проверенной форме, все операторы в регулярной форме удаляются из набора.
Раздел §12.8.20 будет изменен, чтобы отразить влияние, которое оказывает контекст, когда он установлен или снят, на разрешение перегрузки унарного оператора.
Разрешение перегрузки двоичных операторов
Первый пункт в разделе §12.4.5 будет изменен следующим образом (дополнения выделены полужирным шрифтом).
- Определяется набор пользовательских определённых операторов, предложенных
X
иY
для операцииoperator op(x,y)
. Набор состоит из объединения множеств операторов-кандидатов, предоставляемыхX
, и операторов-кандидатов, предоставляемыхY
, каждый из которых определяется с использованием правил, приведенных в разделе "Кандидаты определяемых пользователем операторов" ниже. ЕслиX
иY
одного типа илиX
иY
производные от общего базового типа, то общие оператор-кандидаты встречаются в объединенной совокупности только один раз. Если набор содержит по крайней мере один оператор в проверенной форме, все операторы в регулярной форме удаляются из набора.
Операторы с проверкой и без проверки §12.8.20 будут скорректированы, чтобы отразить влияние проверяемого или непроверяемого контекста на разрешение перегрузки двоичных операторов.
Операторы, определенные пользователем и являющиеся кандидатами
§12.4.6 Раздел " - кандидатов на определяемые пользователем операторы" будет изменён следующим образом (новые добавления выделены полужирным шрифтом).
Учитывая тип T
и операцию operator op(A)
, где op
является перегруженным оператором и A
— списком аргументов, набор кандидатов пользовательских операторов, предоставляемых T
для operator op(A)
, определяется следующим образом:
- Определите тип
T0
. ЕслиT
является типом, допускаемым значением NULL,T0
является его базовым типом, в противном случаеT0
равноT
. - Для всех объявлений
operator op
в их проверенной и регулярной формах в контексте оценкиchecked
и в их регулярной форме только в контексте оценкиunchecked
вT0
и всех поднятых формах таких операторов, если хотя бы один оператор применим (§12.6.4.2) относительно списка аргументовA
, то набор операторов-кандидатов состоит из всех таких применимых операторов вT0
. - В противном случае, если
T0
равноobject
, набор кандидатных операторов пуст. - В противном случае набор операторов кандидатов, предоставляемых
T0
, является набором операторов кандидатов, предоставляемых прямым базовым классомT0
, или эффективным базовым классомT0
, еслиT0
является параметром типа.
При определении набора операторов-кандидатов в интерфейсах https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md#shadowing-within-interfacesприменяется аналогичная фильтрация.
Раздел §12.8.20 будет изменен, чтобы отразить эффект, который установленный или снятый контекст имеет для разрешения перегрузки унарных и бинарных операторов.
Пример #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);
}
Пример #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
{
}
Пример #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
{
}
Пример #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();
}
Пример #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();
}
Обработка явных преобразований, определяемых пользователем
Третий маркер в §10.5.5:
- Найдите набор применимых пользовательских определённых и поднятых операторов преобразования,
U
. Этот набор состоит из определяемых пользователем и поднятых неявных или явных операторов преобразования, объявленных классами или структурами вD
, которые преобразуются из типа, охватывающего или охватываемогоS
, в тип, охватывающий или охватываемыйT
. ЕслиU
пуст, преобразование не определено и возникает ошибка во время компиляции.
будет заменено следующими маркированными пунктами:
- Найдите набор применимых пользовательских и снятых явных операторов преобразования,
U0
. Этот набор состоит из определяемых пользователем и снятых явных операторов преобразования, объявленных классами или структурами вD
в их проверяемых и обычных формах в контексте оценкиchecked
и только в обычной форме вunchecked
в контексте оценки, и преобразование из типа, охватывающего или охватываемогоS
, в тип, охватывающий или охватываемыйT
. - Если
U0
содержит по крайней мере один оператор в проверенной форме, все операторы в регулярной форме удаляются из набора. - Найдите набор применимых пользовательских и снятых операторов преобразования,
U
. Этот набор состоит из операторов изU0
, а также из пользовательских и поднятых неявных операторов преобразования, объявленных классами или структурами вD
, которые преобразуют из типа, охватывающего или охватываемогоS
, в тип, охватывающий или охватываемыйT
. ЕслиU
пуст, преобразование не определено и возникает ошибка во время компиляции.
Операторы checked и unchecked §12.8.20 будут изменены, чтобы отразить влияние, которое контекст checked/unchecked оказывает на обработку определяемых пользователем явных преобразований.
Проверенный или непроверенный контекст в checked operator
Компилятор может рассматривать контекст по умолчанию checked operator
как проверенный. Разработчику потребуется явно использовать unchecked
, если часть алгоритма не должна участвовать в checked context
. Однако это может не сработать в будущем, если мы начнем разрешать использовать токены checked
/unchecked
в качестве модификаторов операторов, чтобы установить контекст внутри тела. Модификатор и ключевое слово могут противоречить друг другу. Кроме того, мы не смогли бы иметь ту же возможность (рассматривать контекст по умолчанию как неотмеченный) для regular operator
, потому что это будет ломающее изменение.
Неразрешенные вопросы
Следует ли языку разрешать checked
и unchecked
модификаторы методов (например, static checked void M()
)?
Это позволит удалить уровни вложенности для методов, которые в этом нуждаются.
Проверенное разделение в деревах выражений Linq
Нет фабричного метода для создания проверяемого узла деления, и нет элемента ExpressionType.DivideChecked
.
Мы все еще можем использовать следующий фабричный метод для создания обычного узла деления, где MethodInfo
указывает на метод op_CheckedDivision
.
Потребителям придется проверить имя для вывода контекста.
public static BinaryExpression Divide (Expression left, Expression right, MethodInfo? method);
Обратите внимание, что хотя §12.8.20 раздел перечисляет оператор /
в качестве одного из операторов, затронутых контекстом оценки с проверкой/без проверки, IL не имеет специального кода для выполнения проверяемого деления.
Сегодня компилятор всегда использует метод фабрики независимо от контекста.
предложение : проверенное пользователем представление не будет поддерживаться в деревах выражений Linq.
(Разрешено) Следует ли поддерживать неявные проверяемые операторы преобразования?
Как правило, неявные операторы преобразования не должны вызываться.
предложение : Нет.
Решение : утверждено — https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md#checked-implicit-conversions
Совещания по дизайну
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