확인된 사용자 정의 연산자
메모
이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.
기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 언어 디자인 모임(LDM) 노트의에서는 이러한 차이가 기록됩니다.
사양에 관한 문서에서 기능 스펙릿을 C# 언어 표준으로 채택하는 과정에 대해 자세히 알아볼 수 있습니다.
요약
C#에서는 사용자가 오버플로 동작을 적절하게 옵트인 또는 아웃할 수 있도록 다음 사용자 정의 연산자의 checked
변형을 정의할 수 있도록 지원해야 합니다.
동기
사용자가 형식을 선언하고 선택된 버전과 선택되지 않은 연산자 버전을 모두 지원할 수 있는 방법은 없습니다. 이렇게 하면 라이브러리 팀에서 노출하는 제안된 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
예외를 throw해야 합니다. 실제로 너무 크다는 것이 무엇을 의미하는지는 대상 유형의 조건에 따라 달라지며, 언어에 의해 규정되지 않습니다. 일반적으로 발생하는 예외는 System.OverflowException
이지만, 언어 자체에 이와 관련된 특정 요구 사항은 없습니다.
대상 형식에서 나타낼 수 없을 정도로 작업 결과가 너무 클 경우, 사용자 정의 요소 regular operator
는 예외를 throw하지 않습니다. 대신 잘린 결과를 나타내는 인스턴스를 반환해야 합니다. 너무 크거나 잘리는 것이 실제로 어떤 의미인지 여부는 대상 타입의 특성에 따라 달라지며, 이는 언어 자체에 의해 정해진 것이 아닙니다.
지원되는 checked
형식이 있는 기존의 모든 사용자 정의 연산자는 regular operators
범주에 속합니다. 이들 중 상당수는 위에서 지정한 의미 체계를 따르지 않을 가능성이 높지만 의미 체계 분석을 위해 컴파일러는 이를 가정합니다.
checked operator
내에서 확인된 맥락과 확인되지 않은 맥락
checked operator
본문 내에서 체크됨/체크 해제됨 컨텍스트는 checked
키워드의 존재에 영향을 받지 않습니다. 즉, 컨텍스트는 연산자 선언의 시작 부분과 즉시 동일합니다. 알고리즘의 일부가 기본 컨텍스트를 사용할 수 없는 경우 개발자는 컨텍스트를 명시적으로 전환해야 합니다.
메타데이터의 이름
ECMA-335의 섹션 "I.10.3.1 단항 연산자"는 검사를 포함하는 단항 연산자인 , 및 을 구현하는 메서드의 이름으로 ++
, --
, -
를 포함하도록 조정됩니다.
ECMA-335의 섹션 "I.10.3.2 이진 연산자"는 확인된 , , 및 이진 연산자를 구현하는 메서드의 이름으로 +
, -
, *
, /
포함하도록 조정됩니다.
ECMA-335의 섹션 "I.10.3.3 변환 연산자"는 확인된 명시적 변환 연산자를 구현하는 메서드의 이름으로 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
선언과 이러한 연산자의 모든 중첩 형식에 대해, 인수 목록 와 관련하여 하나 이상의 연산자(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
. 이 집합은에 있는, 를 포함하거나 그로부터 포괄되는 형식에서 를 포함하거나 그로부터 포괄되는 형식으로 변환하는 사용자 정의 및 해제된 암시적 또는 명시적 변환 연산자로 구성됩니다. U
비어 있으면 변환이 정의되지 않고 컴파일 시간 오류가 발생합니다.
checked 및 unchecked 연산자 §11.8.20 섹션은 체크된/비체크된 컨텍스트가 사용자 정의 명시적 변환 처리에 미치는 영향을 반영하도록 조정됩니다.
연산자 구현
checked operator
는 regular operator
을 구현하지 않으며, 그 반대도 마찬가지입니다.
Linq 표현식 트리
Linq 식 트리에서 Checked operators
이 지원될 것입니다. 해당 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 - 후보 사용자 정의 연산자규칙을 사용하여 결정됩니다.
는 다음 두 개의 글머리 기호로 바뀝니다.
- 현재 체크됨/체크되지 않음 컨텍스트에 맞는 작업
에 대해 가 제공하는 후보 사용자 정의 연산자 집합은 의 후보 사용자 정의 연산자 규칙을 사용하여 결정됩니다. - 후보 사용자 정의 연산자 집합이 비어 있지 않다면, 그 연산자 집합은 작업의 후보 연산자가 됩니다. 그렇지 않으면 반대의 선택/선택되지 않은 컨텍스트 일치하는 작업에 대해
제공한 후보 사용자 정의 연산자 집합은 §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
제공하는 후보 연산자의 조합으로 구성되며, 각각 §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
일치하는 적용 가능한 사용자 정의 및 해제된 명시적 변환 연산자 집합을 찾습니다. 이 집합은 현재 선택/선택되지 않은 컨텍스트 일치하고포함하거나 포괄하는 형식에서 포함하거나 포함하는 형식으로 변환할 있는 클래스 또는 구조체에 의해 선언된 사용자 정의 및 해제된 명시적 변환 연산자로 구성됩니다. - 적용 가능한 사용자 정의 및 리프팅된 명시적 변환 연산자 집합을 체크됨/체크되지 않음 컨텍스트,
U1
에 맞는 것을 찾으십시오.U0
이 비어 있지 않으면U1
이 비어 있습니다. 그렇지 않으면 이 집합은클래스 또는 구조체에 의해 선언된 사용자 정의 및 해제된 명시적 변환 연산자로 구성되며, 이 연산자는 검사된 컨텍스트와 일치하지 않으며 포함하거나 포괄하는 형식에서 포함하거나 포괄하는 형식으로 변환할 있습니다. - 적용 가능한 사용자 정의 변환 연산자 및 리프팅된(확장된) 변환 연산자 집합을 찾으십시오,
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
공통 기본 형식에서 파생된 경우 공유 후보 연산자는 결합된 집합에서 한 번만 발생합니다. 집합에 확인된 형식의 연산자가 하나 이상 포함되어 있으면 일반 형식의 모든 연산자가 집합에서 제거됩니다.
checked 및 unchecked 연산자 §12.8.20 섹션은 체크된/체크되지 않은 컨텍스트가 이진 연산자 오버로드 해석에 미치는 영향을 반영하도록 조정됩니다.
후보 사용자 정의 연산자
§12.4.6 - 후보 사용자 정의 연산자 섹션은 다음과 같이 조정됩니다(추가는 굵게 표시됨).
T
형식과 작업 operator op(A)
이 주어졌을 때, op
이 인수 목록이고 A
가 오버로드 가능한 연산자인 경우, T
에 대해 operator op(A)
가 제공하는 후보 사용자 정의 연산자 집합은 다음과 같이 결정됩니다.
-
T0
형식을 확인합니다.T
nullable 형식이면T0
이 기본 형식이고, 아닌 경우T0
는T
와 같습니다. - 모든
선언은 평가 컨텍스트에서 검사된 형식과 정규 형식으로 평가 컨텍스트의 정규 형식 및 이러한 연산자의 모든 해제된 형식으로만 . 인수 목록 대해 하나 이상의 연산자( §12.6.4.2 )를 적용할 수 있는 경우 후보 연산자 집합은해당 연산자 집합으로 구성됩니다. - 그렇지 않고
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
비어 있으면 변환이 정의되지 않고 컴파일 시간 오류가 발생합니다.
선택하거나 선택하지 않은 연산자 §12.8.20 섹션은 선택/선택되지 않은 컨텍스트가 사용자 정의 명시적 변환 처리에 미치는 영향을 반영하도록 조정됩니다.
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에는 검사된 나누기를 수행하는 특수 op 코드가 없습니다.
컴파일러는 오늘날 어떤 컨텍스트에서도 항상 팩터리 메서드를 사용합니다.
제안: 검증된 사용자 정의 분할은 Linq 식 트리에서 지원되지 않습니다.
(해결됨) 암시적 체크 변환 연산자를 지원해야 하나요?
일반적으로 암시적 변환 연산자는 throw하지 않아야 합니다.
제안: 아니요.
디자인 회의
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