다음을 통해 공유


확인된 사용자 정의 연산자

메모

이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 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이 기본 형식이고, 아닌 경우 T0T와 같습니다.
  • 사용자 정의 연산자 집합 U을 찾습니다. 이 집합은 다음으로 구성됩니다.
    • unchecked 평가 컨텍스트에서 모든 일반 operator op 선언은 T0.
    • checked 평가 컨텍스트에서, 쌍 단위로 일치하는 operator op 선언을 가진 일반 선언을 제외하고, T0의 모든 선택 및 일반 checked operator 선언이 있습니다.
  • operator op의 모든 U 선언과 이러한 연산자의 모든 중첩 형식에 대해, 인수 목록 와 관련하여 하나 이상의 연산자(A)를 적용할 수 있는 경우, 후보 연산자 집합은 T0의 모든 적용 가능한 연산자로 구성됩니다.
  • 그렇지 않고 T0object이면, 후보 연산자의 집합이 비어 있습니다.
  • 그 외에는, 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 operatorregular operator을 구현하지 않으며, 그 반대도 마찬가지입니다.

Linq 표현식 트리

Linq 식 트리에서 Checked operators이 지원될 것입니다. 해당 UnaryExpression사용하여 /BinaryExpressionMethodInfo 노드가 만들어집니다. 다음 팩터리 메서드가 사용됩니다.

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 operatorsunchecked의 맥락에서 적용할 수 없습니다.

컴파일러는 멤버 조회를 수행하여 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_Additionop_AdditionChecked모두 찾습니다. 해당하는 유일한 함수 멤버가 checked operator경우 사용됩니다. regular operatorchecked 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 operatorunchecked 평가 컨텍스트와 일치하고, checked operatorchecked 평가 컨텍스트와 일치하며, checked 형태가 없는 연산자(예: +)가 어느 컨텍스트와도 일치한다고 가정하면, §12.4.4 - 단항 연산자 오버로드 해해결의 첫 번째 항목은입니다.

는 다음 두 개의 글머리 기호로 바뀝니다.

  • 현재 체크됨/체크되지 않음 컨텍스트에 맞는 작업 에 대해 가 제공하는 후보 사용자 정의 연산자 집합은 의 후보 사용자 정의 연산자규칙을 사용하여 결정됩니다.
  • 후보 사용자 정의 연산자 집합이 비어 있지 않다면, 그 연산자 집합은 작업의 후보 연산자가 됩니다. 그렇지 않으면 반대의 선택/선택되지 않은 컨텍스트 일치하는 작업에 대해 제공한 후보 사용자 정의 연산자 집합은 §12.4.6 - 후보 사용자 정의 연산자가규칙을 사용하여 결정됩니다.

이진 연산자 오버로드 해결

regular operator unchecked 평가 컨텍스트와 일치하고, checked operatorchecked 평가 컨텍스트와 일치하며, checked 형태가 없는 연산자(예: %)가 어느 컨텍스트와도 일치한다고 가정할 때, §12.4.5의 첫 번째 글머리 기호 - 이진 연산자 오버로드 확인.

  • XY이 제공하는 작업 operator op(x,y)에 대한 후보 사용자 정의 연산자 집합이 결정됩니다. 집합은 X 제공하는 후보 연산자와 Y제공하는 후보 연산자의 조합으로 구성되며, 각각 §12.4.6 - 후보 사용자 정의 연산자규칙을 사용하여 결정됩니다. XY 동일한 형식이거나 XY 공통 기본 형식에서 파생된 경우 공유 후보 연산자는 결합된 집합에서 한 번만 발생합니다.

는 다음 두 개의 글머리 기호로 바뀝니다.

  • 현재 체크됨/체크되지 않음 컨텍스트에 맞춰 작업을 위한 이 제공하는 후보 사용자 정의 연산자 집합이 결정됩니다. 집합은 X 제공하는 후보 연산자와 Y제공하는 후보 연산자의 조합으로 구성되며, 각각 §12.4.6 - 후보 사용자 정의 연산자규칙을 사용하여 결정됩니다. XY 동일한 형식이거나 XY 공통 기본 형식에서 파생된 경우 공유 후보 연산자는 결합된 집합에서 한 번만 발생합니다.
  • 후보 사용자 정의 연산자 집합이 비어 있지 않다면, 그 연산자 집합은 작업의 후보 연산자가 됩니다. 그렇지 않은 경우, XY이 제공한 후보 사용자 정의 연산자 집합은 operator op(x,y)작업을 위해 반대되는 체크됨/체크되지 않음 컨텍스트와 일치하는 것으로 결정됩니다. 집합은 X 제공하는 후보 연산자와 Y제공하는 후보 연산자의 조합으로 구성되며, 각각 §12.4.6 - 후보 사용자 정의 연산자규칙을 사용하여 결정됩니다. XY 동일한 형식이거나 XY 공통 기본 형식에서 파생된 경우 공유 후보 연산자는 결합된 집합에서 한 번만 발생합니다.
예제 #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 operatorchecked 평가 컨텍스트와 일치한다고 가정하면 사용자 정의 변환의 평가에 대한 §10.5.3의 세 번째 항목는 다음과 같습니다.

  • 적용 가능한 사용자 정의 변환 연산자 및 리프팅된(확장된) 변환 연산자 집합을 찾으십시오, U. 이 집합은 D을 포함하거나 다루는 형식에서 S를 포함하거나 다루는 형식으로 변환하는, T 클래스나 구조체에서 선언된 사용자 정의 및 확장된 암시적 또는 명시적 변환 연산자로 구성됩니다. U 비어 있으면 변환이 정의되지 않고 컴파일 시간 오류가 발생합니다.

는 다음 글머리 기호로 바뀝니다.

  • 현재 선택/선택되지 않은 컨텍스트 U0일치하는 적용 가능한 사용자 정의 및 해제된 명시적 변환 연산자 집합을 찾습니다. 이 집합은 현재 선택/선택되지 않은 컨텍스트 일치하고 포함하거나 포괄하는 형식에서 포함하거나 포함하는 형식으로 변환할 있는 클래스 또는 구조체에 의해 선언된 사용자 정의 및 해제된 명시적 변환 연산자로 구성됩니다.
  • 적용 가능한 사용자 정의 및 리프팅된 명시적 변환 연산자 집합을 체크됨/체크되지 않음 컨텍스트, U1에 맞는 것을 찾으십시오. U0이 비어 있지 않으면 U1이 비어 있습니다. 그렇지 않으면 이 집합은 클래스 또는 구조체에 의해 선언된 사용자 정의 및 해제된 명시적 변환 연산자로 구성되며, 이 연산자는 검사된 컨텍스트와 일치하지 않으며 포함하거나 포괄하는 형식에서 포함하거나 포괄하는 형식으로 변환할 있습니다.
  • 적용 가능한 사용자 정의 변환 연산자 및 리프팅된(확장된) 변환 연산자 집합을 찾으십시오, U. 이 집합은 U0U1의 연산자와, D의 클래스 또는 구조체가 선언한 사용자 정의 및 리프팅된 암시적 변환 연산자로 구성됩니다. 이러한 연산자는 S을 포함하거나 포괄하는 형식에서 T를 포함하거나 포괄하는 형식으로 변환합니다. U 비어 있으면 변환이 정의되지 않고 컴파일 시간 오류가 발생합니다.

또 한 번 더 후보 사용자 정의 연산자 집합을 빌드하는 방법

단항 연산자 오버로드 해결

섹션 §12.4.4의 첫 번째 글머리 기호는 다음과 같이 조정됩니다(추가 사항은 굵게 표시됩니다).

  • 작업 X에 대해 operator op(x)가 제공하는 후보 사용자 정의 연산자의 집합은 아래의 "후보 사용자 정의 연산자" 섹션의 규칙을 사용하여 결정됩니다. 집합에 확인된 형식의 연산자가 하나 이상 포함되어 있으면 일반 형식의 모든 연산자가 집합에서 제거됩니다.

§12.8.20 섹션은 검사/비검사 컨텍스트가 단항 연산자 오버로드 해상도에 미치는 영향을 반영하도록 조정됩니다.

이진 연산자 오버로드 해결

섹션 §12.4.5의 첫 번째 글머리 기호는 다음과 같이 수정됩니다. (추가 내용은 굵게 표시됩니다.)

  • XY이 제공하는 작업 operator op(x,y)에 대한 후보 사용자 정의 연산자 집합이 결정됩니다. 집합은 X 제공하는 후보 연산자와 Y제공한 후보 연산자의 합으로 구성되며, 각 연산자는 아래의 "후보 사용자 정의 연산자" 섹션의 규칙을 사용하여 결정됩니다. XY 동일한 형식이거나 XY 공통 기본 형식에서 파생된 경우 공유 후보 연산자는 결합된 집합에서 한 번만 발생합니다. 집합에 확인된 형식의 연산자가 하나 이상 포함되어 있으면 일반 형식의 모든 연산자가 집합에서 제거됩니다.

checked 및 unchecked 연산자 §12.8.20 섹션은 체크된/체크되지 않은 컨텍스트가 이진 연산자 오버로드 해석에 미치는 영향을 반영하도록 조정됩니다.

후보 사용자 정의 연산자

§12.4.6 - 후보 사용자 정의 연산자 섹션은 다음과 같이 조정됩니다(추가는 굵게 표시됨).

T 형식과 작업 operator op(A)이 주어졌을 때, op이 인수 목록이고 A가 오버로드 가능한 연산자인 경우, T에 대해 operator op(A)가 제공하는 후보 사용자 정의 연산자 집합은 다음과 같이 결정됩니다.

  • T0형식을 확인합니다. T nullable 형식이면 T0이 기본 형식이고, 아닌 경우 T0T와 같습니다.
  • 모든 선언은 평가 컨텍스트에서 검사된 형식과 정규 형식으로 평가 컨텍스트의 정규 형식 및 이러한 연산자의 모든 해제된 형식으로만 . 인수 목록 대해 하나 이상의 연산자(§12.6.4.2)를 적용할 수 있는 경우 후보 연산자 집합은 해당 연산자 집합으로 구성됩니다.
  • 그렇지 않고 T0object이면, 후보 연산자의 집합이 비어 있습니다.
  • 그 외에는, 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 대해 동일한 작업을 수행할 수 없습니다(기본 컨텍스트를 선택하지 않은 것으로 처리).

해결되지 않은 질문

언어에서 메서드에 대한 checkedunchecked 한정자를 허용해야 하나요(예: 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#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