Microsoft MVP성태의 닷넷 이야기
.NET Framework: 2031. C# 11 - 사용자 정의 checked 연산자 [링크 복사], [링크+제목 복사]
조회: 7797
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  9  10  11  12  13  14  [15]  ...
NoWriterDateCnt.TitleFile(s)
13248정성태2/7/20234052오류 유형: 841. 리눅스 - [사용자 계정] is not in the sudoers file. This incident will be reported.
13247정성태2/7/20234964VS.NET IDE: 180. Visual Studio - 닷넷 소스 코드 디버깅 중 "Decompile source code"가 동작하는 않는 문제
13246정성태2/6/20234090개발 환경 구성: 664. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 - 두 번째 이야기
13245정성태2/6/20234639.NET Framework: 2093. C# - PEM 파일을 이용한 RSA 개인키/공개키 설정 방법파일 다운로드1
13244정성태2/5/20233990VS.NET IDE: 179. Visual Studio - External Tools에 Shell 내장 명령어 등록
13243정성태2/5/20234856디버깅 기술: 190. windbg - Win32 API 호출 시점에 BP 거는 방법 [1]
13242정성태2/4/20234304디버깅 기술: 189. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.UnauthorizedAccessException
13241정성태2/3/20233827디버깅 기술: 188. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.IO.FileNotFoundException
13240정성태2/1/20233987디버깅 기술: 187. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.Web.HttpException
13239정성태2/1/20233629디버깅 기술: 186. C# - CacheDependency의 숨겨진 예외 - System.Web.HttpException
13238정성태1/31/20235641.NET Framework: 2092. IIS 웹 사이트를 TLS 1.2 또는 TLS 1.3 프로토콜로만 운영하는 방법
13237정성태1/30/20235329.NET Framework: 2091. C# - 웹 사이트가 어떤 버전의 TLS/SSL을 지원하는지 확인하는 방법
13236정성태1/29/20234972개발 환경 구성: 663. openssl을 이용해 인트라넷 IIS 사이트의 SSL 인증서 생성
13235정성태1/29/20234515개발 환경 구성: 662. openssl - 윈도우 환경의 명령행에서 SAN 적용하는 방법
13234정성태1/28/20235562개발 환경 구성: 661. dnSpy를 이용해 소스 코드가 없는 .NET 어셈블리의 코드를 변경하는 방법 [1]
13233정성태1/28/20236904오류 유형: 840. C# - WebClient로 https 호출 시 "The request was aborted: Could not create SSL/TLS secure channel" 예외 발생
13232정성태1/27/20234704스크립트: 43. uwsgi의 --processes와 --threads 옵션
13231정성태1/27/20233618오류 유형: 839. python - TypeError: '...' object is not callable
13230정성태1/26/20234031개발 환경 구성: 660. WSL 2 내부로부터 호스트 측의 네트워크로 UDP 데이터가 1개의 패킷으로만 제한되는 문제
13229정성태1/25/20234975.NET Framework: 2090. C# - UDP Datagram의 최대 크기
13228정성태1/24/20235120.NET Framework: 2089. C# - WMI 논리 디스크가 속한 물리 디스크의 정보를 얻는 방법 [2]파일 다운로드1
13227정성태1/23/20234824개발 환경 구성: 659. Windows - IP MTU 값을 바꿀 수 있을까요? [1]
13226정성태1/23/20234497.NET Framework: 2088. .NET 5부터 지원하는 GetRawSocketOption 사용 시 주의할 점
13225정성태1/21/20233749개발 환경 구성: 658. Windows에서 실행 중인 소켓 서버를 다른 PC 또는 WSL에서 접속할 수 없는 경우
13224정성태1/21/20234098Windows: 221. Windows - Private/Public/Domain이 아닌 네트워크 어댑터 단위로 방화벽을 on/off하는 방법
13223정성태1/20/20234288오류 유형: 838. RDP 연결 오류 - The two computers couldn't connect in the amount of time allotted
1  2  3  4  5  6  7  8  9  10  11  12  13  14  [15]  ...