Microsoft MVP성태의 닷넷 이야기
.NET Framework: 2031. C# 11 - 사용자 정의 checked 연산자 [링크 복사], [링크+제목 복사],
조회: 16882
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 18개 있습니다.)
.NET Framework: 1110. C# 11 - 인터페이스 내에 정적 추상 메서드 정의 가능 (DIM for Static Members)
; https://www.sysnet.pe.kr/2/0/12814

.NET Framework: 1118. C# 11 - 제네릭 타입의 특성 적용
; https://www.sysnet.pe.kr/2/0/12839

.NET Framework: 1182. C# 11  - ref struct에 ref 필드를 허용
; https://www.sysnet.pe.kr/2/0/13015

.NET Framework: 2025. C# 11  - 원시 문자열 리터럴(raw string literals)
; https://www.sysnet.pe.kr/2/0/13085

.NET Framework: 2026. C# 11 - 문자열 보간 개선 2가지
; https://www.sysnet.pe.kr/2/0/13086

.NET Framework: 2030. C# 11 - UTF-8 문자열 리터럴
; https://www.sysnet.pe.kr/2/0/13096

.NET Framework: 2031. C# 11 - 사용자 정의 checked 연산자
; https://www.sysnet.pe.kr/2/0/13099

.NET Framework: 2032. C# 11 - shift 연산자 재정의에 대한 제약 완화 (Relaxing Shift Operator)
; https://www.sysnet.pe.kr/2/0/13100

.NET Framework: 2035. C# 11 - 새로운 연산자 ">>>" (Unsigned Right Shift)
; https://www.sysnet.pe.kr/2/0/13110

.NET Framework: 2036. C# 11 - IntPtr/UIntPtr과 nint/nuint의 통합
; https://www.sysnet.pe.kr/2/0/13111

.NET Framework: 2037. C# 11 - 목록 패턴(List patterns)
; https://www.sysnet.pe.kr/2/0/13112

.NET Framework: 2038. C# 11 - Span 타입에 대한 패턴 매칭 (Pattern matching on ReadOnlySpan<char>)
; https://www.sysnet.pe.kr/2/0/13113

.NET Framework: 2042. C# 11 - 파일 범위 내에서 유효한 타입 정의 (File-local types)
; https://www.sysnet.pe.kr/2/0/13117

.NET Framework: 2045. C# 11 - 메서드 매개 변수에 대한 nameof 지원
; https://www.sysnet.pe.kr/2/0/13122

.NET Framework: 2046. C# 11 - 멤버(속성/필드)에 지정할 수 있는 required 예약어 추가
; https://www.sysnet.pe.kr/2/0/13123

.NET Framework: 2048. C# 11 - 구조체 필드의 자동 초기화(auto-default structs)
; https://www.sysnet.pe.kr/2/0/13125

.NET Framework: 2049. C# 11 - 정적 메서드에 대한 delegate 처리 시 cache 적용
; https://www.sysnet.pe.kr/2/0/13126

.NET Framework: 2102. C# 11 - ref struct/ref field를 위해 새롭게 도입된 scoped 예약어
; https://www.sysnet.pe.kr/2/0/13276




C# 11 - 사용자 정의 checked 연산자

(Visual Studio 2022 17.3 이후 버전에서 테스트할 수 있습니다.)

현재 overflow 체크는 다음의 동작에서 영향을 주는데요,

  • 정수형 타입에 대한 ++, --, 음수 부호 -, 4칙 연산자(+, -, *, /)
  • 정수 형식끼리의 형변환, 또는 float, double 타입을 정수형 타입에 형변환

단, 이미 만들어진 타입에 한해서만 동작을 할 뿐 사용자 정의 타입에서는 overflow 제어를 위한 checked/unchecked 구문에 참여할 수 없습니다.

예를 들어 볼까요?

checked {
    sbyte a = 127;
    a++; // Unhandled exception. System.OverflowException: Arithmetic operation resulted in an overflow.
}

위의 코드는 (컴파일 시점이 아닌) 실행 시에 System.OverflowException 예외가 발생합니다. 반면, checked 예약어를 제거하면 아무런 오류 없이 a의 값은 -128로 바뀝니다.

즉, C#은 기본적으로 overflow를 체크하지 않는 코드를 생성합니다. 이러한 기본값은 csproj의 CheckForOverflowUnderflow 옵션을 이용하면 변경할 수 있습니다.

<Project Sdk="Microsoft.NET.Sdk">

...[생략]...
        <LangVersion>preview</LangVersion>
        <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
...[생략]...

</Project>

그래서 위와 같이 설정한 후부터는 다음의 코드도 런타임 시에 System.OverflowException이 발생합니다.

sbyte a = 127;
a++; // CheckForOverflowUnderflow 옵션이 true인 경우 System.OverflowException 예외 발생

그런데, 이 상황에서도 예외가 발생하지 않도록 하는 방법이 있습니다. 바로 unchecked를 설정하는 것입니다.

unchecked {
    sbyte a = 127;
    a++; // CheckForOverflowUnderflow 옵션이 true여도, 예외 발생하지 않고 a == -128
}

대충 CheckForOverflowUnderflow, checked, unchecked 관계를 아시겠죠? ^^




그런데 위와 같은 overflow 처리 정책을 우리가 만드는 C# 클래스에서는 따를 수가 없습니다. 가령 3 바이트 정수형 타입을 정의한다고 했을 때,

public struct Int3
{
    int value;

    public Int3(int value)
    {
        this.value = value;
    }

    public static implicit operator Int3(int value) => new Int3(value);

    public static Int3 operator ++(Int3 lhs)
    {
        return new Int3(lhs.value + 1);
    }

    public override string ToString()
    {
        return $"{value}";
    }
}

이렇게 사용한다고 해서 overflow 예외가 발생할 수는 없습니다.

checked
{
    Int3 n = 0x007F_FFFF;
    n++; // n == 8388608
}

당연한 결과인데요, 이때 개발자는 checked/unchecked 문맥과 상관없이 그냥 무조건 overflow 예외를 발생하는 정도로 구현할 수는 있습니다.

public static Int3 operator ++(Int3 lhs)
{
    if (lhs.value + 1 > 8388607)
    {
        throw new OverflowException((lhs.value + 1).ToString());
    }

    return new Int3(lhs.value + 1);
}

그래서 설령 unchecked여도,

unchecked
{
    Int3 n = 0x007F_FFFF;
    n++; // 또는 CheckForOverflowUnderflow 옵션이 false여도 무조건 OverflowException 예외 발생
}

overflow 처리가 되는 것입니다.




이 문제를 해결하려면, 근본적으로 C# 컴파일러와의 연동이 필요한데 애당초 C# 컴파일러는 현재 코드 영역이 checked/unchecked 인지 파악할 수 있기 때문입니다. 그렇다면 이제 필요한 것은, C# 컴파일러가 overflow 문맥에 따라 생성할 코드도 함께 checked 또는 unchecked 유형에 맞는 것으로 고를 수 있게 해주면 됩니다. 이를 위해 C# 11부터 추가된 것이 바로 "checked" 유형의 연산자 재정의입니다. 그리하여, 위에서 정의한 Int3 구조체에 다음과 같이 2가지 overflow 문맥에 따른 연산자를 제공할 수 있고,

public static Int3 operator checked ++(Int3 lhs)
{
    if (lhs.value + 1 > 8388607)
    {
        throw new OverflowException((lhs.value + 1).ToString());
    }

    return new Int3(lhs.value + 1);
}

public static Int3 operator ++(Int3 lhs) => new Int3(lhs.value + 1);

결국 C# 컴파일러는 대상 코드 영역의 overflow 문맥에 따라 "operator checked ++" 또는 "operator ++" 호출 코드를 적절하게 선택해 빌드를 하게 됩니다.




checked 연산자 재정의가 된 경우 내부 메서드 이름에는 "Checked"가 붙습니다. 가령 위에서 정의한 ++ 연산자의 경우 원래 이름은 "op_Increment"지만 checked 버전의 경우 "op_CheckedIncrement"라는 이름으로 정의됩니다.

또한, 새롭게 추가한 연산자를 "checked operator"라고 하고 기존의 연산자를 "regular operator"라고 부릅니다. 이러한 연산자는 1개 또는 2개를 제공할 수 있지만 "checked operator" 하나만을 제공할 수는 없습니다. (이것은 C# 컴파일러에서의 제약이고, 다른 언어라면 설계에 따라 그것이 가능할 수도 있습니다. 즉, IL 언어 수준에서의 제약은 아닙니다.)

public struct Int3
{
    // checked와 regular 연산자 모두 제공하면 OK (checked/unchecked 문맥에 따라 다른 메서드가 선택됨)
    public static Int3 operator checked +(Int3 lhs, Int3 rhs);
    public static Int3 operator +(Int3 lhs, Int3 rhs);

    // 혹은, regular 연산자 하나만 제공해도 OK (기존처럼 동작)
    public static Int3 operator -(Int3 lhs, Int3 rhs);

    // 하지만 checked 연산자 하나만 제공하는 경우 컴파일 에러
    // error CS9025: The operator 'Int3.operator checked ++(Int3)' requires a matching non-checked version of the operator to also be defined
    public static Int3 operator checked *(Int3 lhs, Int3 rhs);
}

아래의 코드는 부호 연산자 -, 4칙 연산자 *, 증가 연산자 ++를 재정의한 Int3 타입의 예를 보여줍니다.

public struct Int3
{
    public static Int3 MaxValue = new Int3(8388607);
    public static Int3 MinValue = new Int3(-8388608);

    int value;

    public Int3(int value) : this(value, false) { }

    public Int3(int value, bool checkOverflow)
    {
        int checkValue = value;

        if (value > 8388607)
        {
            checkValue = -(value & 0x00FF_FFFF);
        }
        else if (value < -8388608)
        {
            checkValue = value & 0x007F_FFFF;
        }

        if (checkOverflow == true)
        {
            if (value != checkValue)
            {
                throw new OverflowException($"{checkValue}");
            }
        }

        this.value = checkValue;
    }

    public static implicit operator Int3(int value) => new Int3(value);

    public static Int3 operator checked ++(Int3 lhs) => new Int3(lhs.value + 1, true);
    public static Int3 operator ++(Int3 lhs) => new Int3(lhs.value + 1);

    public static Int3 operator checked *(Int3 lhs, Int3 rhs) => new Int3(lhs.value * rhs.value, true);
    public static Int3 operator *(Int3 lhs, Int3 rhs) => new Int3(lhs.value * rhs.value);

    public static Int3 operator checked -(Int3 lhs) => new Int3(-lhs.value, true);
    public static Int3 operator -(Int3 lhs) => new Int3(-lhs.value);


    public override string ToString()
    {
        return $"{value}";
    }
}

(첨부 파일은 이 글의 예제 코드를 포함합니다.)

구현 가능한 checked 버전의 연산자는 ++, --, 음수 부호 연산자(-), 사칙 연산자(+, -, *, /)입니다.

마지막으로, 위의 코드를 구현하면서 "implicit operator checked" 형변환 연산자를 재정의할 수 있다면 "public Int3(int value, bool checkOverflow) {...}" 생성자를 굳이 구현할 필요가 없어 편리하겠다는 생각을 했는데요, 이에 대해 C# 개발자들은 다음과 같이 답변하고 있습니다.

질문: (Resolved) Should we support implicit checked conversion operators?

답변: In general, implicit conversion operators are not supposed to throw.

Proposal: No.

Resolution: Approved - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-02-07.md#checked-implicit-conversions

또한 위의 링크에 더 자세한 답변이 나옵니다. ^^

Checked implicit conversions?

We clarified why conversions cannot be implicit and checked. Generally, implicit conversions should not fail, throw or be lossy. That's already the case with (most) existing conversions and .NET guidelines. There's an exception: integer to floating point conversion in C# does allow loss.

Conclusion: Stick with proposed restriction (conversions cannot be implicit and checked).





C# 11 - 인터페이스 내에 정적 추상 메서드 정의 가능 (공식 문서, Static Abstract Members In Interfaces C# 10 Preview)
; https://www.sysnet.pe.kr/2/0/12814

C# 11 - 제네릭 타입의 특성 적용 (공식 문서, Generic attributes)
; https://www.sysnet.pe.kr/2/0/12839

C# 11 - 사용자 정의 checked 연산자 (공식 문서, Checked user-defined operators)
; https://www.sysnet.pe.kr/2/0/13099

C# 11 - shift 연산자 재정의에 대한 제약 완화 (공식 문서, Relaxing Shift Operator)
; https://www.sysnet.pe.kr/2/0/13100

C# 11 - IntPtr/UIntPtr과 nint/unint의 통합 (공식 문서, Numeric IntPtr)
; https://www.sysnet.pe.kr/2/0/13111

C# 11 - 새로운 연산자 ">>>" (Unsigned Right Shift) (공식 문서, Unsigned right shift operator)
; https://www.sysnet.pe.kr/2/0/13110

C# 11 - 원시 문자열 리터럴 (공식 문서, raw string literals)
; https://www.sysnet.pe.kr/2/0/13085

C# 11 - 문자열 보간 개선 2가지 (공식 문서, Allow new-lines in all interpolations)
; https://www.sysnet.pe.kr/2/0/13086

C# 11 - 목록 패턴 (공식 문서, List patterns)
; https://www.sysnet.pe.kr/2/0/13112

C# 11 - Span 타입에 대한 패턴 매칭 (공식 문서, Pattern matching on ReadOnlySpan<char>)
; https://www.sysnet.pe.kr/2/0/13113

C# 11 - Utf8 문자열 리터럴 지원 (공식 문서, Utf8 Strings Literals)
; https://www.sysnet.pe.kr/2/0/13096

C# 11 - ref struct에 ref 필드를 허용 (공식 문서, ref fields)
; https://www.sysnet.pe.kr/2/0/13015

C# 11 - 파일 범위 내에서 유효한 타입 정의 (공식 문서, File-local types)
; https://www.sysnet.pe.kr/2/0/13117

C# 11 - 메서드 매개 변수에 대한 nameof 지원 (공식 문서, nameof(parameter))
; https://www.sysnet.pe.kr/2/0/13122

C# 11 - 멤버(속성/필드)에 지정할 수 있는 required 예약어 추가 (공식 문서, Required members)
; https://www.sysnet.pe.kr/2/0/13123

C# 11 - 구조체 필드의 자동 초기화 (공식 문서, auto-default structs)
; https://www.sysnet.pe.kr/2/0/13125

C# 11 - 정적 메서드에 대한 delegate 처리 시 cache 적용 (공식 문서, Cache delegates for static method group)
; https://www.sysnet.pe.kr/2/0/13126

Language Feature Status
; https://github.com/dotnet/roslyn/blob/main/docs/Language%20Feature%20Status.md




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 5/5/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... [76]  77  78  79  80  81  82  83  84  85  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
12036정성태10/14/201925525.NET Framework: 866. C# - 고성능이 필요한 환경에서 GC가 발생하지 않는 네이티브 힙 사용파일 다운로드1
12035정성태10/13/201919625개발 환경 구성: 461. C# 8.0의 #nulable 관련 특성을 .NET Framework 프로젝트에서 사용하는 방법 [2]파일 다운로드1
12034정성태10/12/201918964개발 환경 구성: 460. .NET Core 환경에서 (프로젝트가 아닌) C# 코드 파일을 입력으로 컴파일하는 방법 [1]
12033정성태10/11/201923176개발 환경 구성: 459. .NET Framework 프로젝트에서 C# 8.0/9.0 컴파일러를 사용하는 방법
12032정성태10/8/201919287.NET Framework: 865. .NET Core 2.2/3.0 웹 프로젝트를 IIS에서 호스팅(Inproc, out-of-proc)하는 방법 - AspNetCoreModuleV2 소개
12031정성태10/7/201916575오류 유형: 569. Azure Site Extension 업그레이드 시 "System.IO.IOException: There is not enough space on the disk" 예외 발생
12030정성태10/5/201923382.NET Framework: 864. .NET Conf 2019 Korea - "닷넷 17년의 변화 정리 및 닷넷 코어 3.0" 발표 자료 [1]파일 다운로드1
12029정성태9/27/201924216제니퍼 .NET: 29. Jennifersoft provides a trial promotion on its APM solution such as JENNIFER, PHP, and .NET in 2019 and shares the examples of their application.
12028정성태9/26/201919176.NET Framework: 863. C# - Thread.Suspend 호출 시 응용 프로그램 hang 현상을 해결하기 위한 시도파일 다운로드1
12027정성태9/26/201914852오류 유형: 568. Consider app.config remapping of assembly "..." from Version "..." [...] to Version "..." [...] to solve conflict and get rid of warning.
12026정성태9/26/201920254.NET Framework: 862. C# - Active Directory의 LDAP 경로 및 정보 조회
12025정성태9/25/201918583제니퍼 .NET: 28. APM 솔루션 제니퍼, PHP, .NET 무료 사용 프로모션 2019 및 적용 사례 (8) [1]
12024정성태9/20/201920514.NET Framework: 861. HttpClient와 HttpClientHandler의 관계 [2]
12023정성태9/18/201920967.NET Framework: 860. ServicePointManager.DefaultConnectionLimit와 HttpClient의 관계파일 다운로드1
12022정성태9/12/201924910개발 환경 구성: 458. C# 8.0 (Preview) 신규 문법을 위한 개발 환경 구성 [3]
12021정성태9/12/201940719도서: 시작하세요! C# 8.0 프로그래밍 [4]
12020정성태9/11/201923895VC++: 134. SYSTEMTIME 값 기준으로 특정 시간이 지났는지를 판단하는 함수
12019정성태9/11/201917445Linux: 23. .NET Core + 리눅스 환경에서 Environment.CurrentDirectory 접근 시 주의 사항
12018정성태9/11/201916248오류 유형: 567. IIS - Unrecognized attribute 'targetFramework'. Note that attribute names are case-sensitive. (D:\lowSite4\web.config line 11)
12017정성태9/11/201920052오류 유형: 566. 비주얼 스튜디오 - Failed to register URL "http://localhost:6879/" for site "..." application "/". Error description: Access is denied. (0x80070005)
12016정성태9/5/201920058오류 유형: 565. git fetch - warning: 'C:\ProgramData/Git/config' has a dubious owner: '(unknown)'.
12015정성태9/3/201925481개발 환경 구성: 457. 윈도우 응용 프로그램의 Socket 연결 시 time-out 시간 제어
12014정성태9/3/201919244개발 환경 구성: 456. 명령행에서 AWS, Azure 등의 원격 저장소에 파일 관리하는 방법 - cyberduck/duck 소개
12013정성태8/28/201922132개발 환경 구성: 455. 윈도우에서 (테스트) 인증서 파일 만드는 방법 [3]
12012정성태8/28/201926713.NET Framework: 859. C# - HttpListener를 이용한 HTTPS 통신 방법
12011정성태8/27/201926268사물인터넷: 57. C# - Rapsberry Pi Zero W와 PC 간 Bluetooth 통신 예제 코드파일 다운로드1
... [76]  77  78  79  80  81  82  83  84  85  86  87  88  89  90  ...