Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 5개 있습니다.)
.NET Framework: 673. C#에서 enum을 boxing 없이 int로 변환하기
; https://www.sysnet.pe.kr/2/0/11270

.NET Framework: 740. C#에서 enum을 boxing 없이 int로 변환하기 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/11506

.NET Framework: 779. C# 7.3에서 enum을 boxing 없이 int로 변환하기 - 세 번째 이야기
; https://www.sysnet.pe.kr/2/0/11565

.NET Framework: 1042. C# - enum 값을 int로 암시적(implicit) 형변환하는 방법?
; https://www.sysnet.pe.kr/2/0/12606

.NET Framework: 2131. C# - Source Generator로 해결하는 enum 박싱 문제
; https://www.sysnet.pe.kr/2/0/13384




C# 7.3에서 enum을 boxing 없이 int로 변환하기 - 세 번째 이야기

예전에 쓴 글에 이어서,

C#에서 enum을 boxing 없이 int로 변환하기
; https://www.sysnet.pe.kr/2/0/11270

C#에서 enum을 boxing 없이 int로 변환하기 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/11506

이번에는 C# 7.3부터 새롭게 추가된 문법을 이용하여 저 코드를 개선해 보겠습니다.

우선 첫 번째 글을 정리해 보면, enum 타입을 명시적인 형변환 외에는,

enum States
{
    Wait,
    Run,
}

int n1 = (int)States.Run;
int n2 = (int)States.Wait;

박싱 없이 사용할 수 없다는 것입니다. 그래서 두 번째 글에서와 같이 System.TypedReference와 __makeref 예약어를 사용해 해결해 보았지만, 아쉽게도 __makeref는 다중 런타임에서의 호환성이 부족한 문제가 있습니다.

(Unity가 사용하는) 모노 런타임의 __makeref 오류
; https://www.sysnet.pe.kr/2/0/11564

그런데, 마침 C# 7.3에서 적절한 문법이 하나 나왔습니다. ^^

C# 7.3 - unmanaged(blittable) 제네릭 제약
; https://www.sysnet.pe.kr/2/0/11558

이것을 이용하면 순수 C#의 공식 문법만 사용해 저 문제를 해결할 수 있습니다. 바로 이렇게!

class WrapperObject<TEnum, TValue> where TEnum : unmanaged
{
    TValue[] data;

    public WrapperObject(int count)
    {
        data = new TValue[count];
    }

    public TValue this[TEnum key]
    {
        get { return data[ConvertToIndex(key)]; }
        set { data[ConvertToIndex(key)] = value; }
    }

    public unsafe int ConvertToIndex(TEnum key)
    {
        TEnum *idx = &key;
        int* intPtr = (int*)idx;
        return *intPtr;
    }

    /*
    unsafe int ConvertToIndex(TEnum key)
    {
        System.TypedReference reference = __makeref(key);
        System.TypedReference* pRef = &reference;

        int* valuePtr = (int*)*((IntPtr*)&reference);
        return *valuePtr;
    }
    */
}

TEnum 타입에 unmanaged 제약을 걸었으니, 이를 포인터로 자연스럽게 받아올 수 있고 그 포인터로부터 값을 참조해 반환하면 박싱 연산이 전혀 발생하지 않습니다.

WrapperObject<States, IState> states = new WrapperObject<States, IState>(2);

Console.WriteLine("Run == " + states.ConvertToIndex(States.Run)); // 출력: 0
Console.WriteLine("Wait == " + states.ConvertToIndex(States.Wait)); // 출력: 1




__makeref를 사용할 때와 달리 unmanaged 제약을 사용하면 enum 타입이 byte, short, long 기반이어도 그에 대응해 포인터 연산을 할 수 있어 안전한 참조를 할 수 있습니다. 왜냐하면, unmanaged 제약의 경우 sizeof 연산자를 이용한 크기 반환도 가능하기 때문입니다. 예를 들어, C# 7.2 이전에는 다음과 같이 오류가 발생하는 코드가,

class WrapperObject<TEnum, TValue> where TEnum : struct
{
    // ...[생략]...

    unsafe public WrapperObject(int count)
    {
        data = new TValue[count];
        
        // unmanaged 이외의 제약으로는 sizeof 연산자를 사용할 수 없음.
        // 컴파일 오류 - Error CS0208 Cannot take the address of, get the size of, or declare a pointer to a managed type ('TEnum')
        _enumSize = sizeof(TEnum);
    }
}

C# 7.3의 unmanaged 제약으로는 컴파일할 수 있게 되므로, 이를 근간으로 다음과 같이 enum의 기반 타입에 맞게 포인터 대응을 할 수 있습니다.

class WrapperObject<TEnum, TValue> where TEnum : unmanaged
{
    TValue[] data;
    int _enumSize = 0;

    unsafe public WrapperObject(int count)
    {
        data = new TValue[count];
    
        // C# 7.3 이후 제공되는 unmanaged 제약으로는 sizeof 연산자를 사용 가능
        _enumSize = sizeof(TEnum);
    }

    public TValue this[TEnum key]
    {
        get { return data[ConvertToIndex(key)]; }
        set { data[ConvertToIndex(key)] = value; }
    }

    public unsafe int ConvertToIndex(TEnum key)
    {
        TEnum* idx = &key;

        // enum 타입의 크기에 맞게 포인터 연산
        switch (_enumSize)
        {
            case 1:
                byte* bytePtr = (byte*)idx;
                return *bytePtr;

            case 2:
                short* shortPtr = (short*)idx;
                return *shortPtr;

            case 4:
                int* intPtr = (int*)idx;
                return *intPtr;

            case 8:
                long* longPtr = (long*)idx;
                return (int)*longPtr;
        }

        return -1;
    }
}

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




C# 7.3의 이러한 매끄러운 해결 방법은, 그래도 현재 .NET Core, .NET Framework 이외의 런타임에서는 사용할 수 없습니다. Mono의 경우 아직 C# 7.0조차도 100% 지원하지 못하고 있으므로,

C# Compiler
; http://www.mono-project.com/docs/about-mono/languages/csharp/

모노 런타임을 근간으로 한 Unity 역시 아직 위와 같은 코드를 사용할 수는 없습니다.

그래도 ^^ __makeref가 지원되는 것을 Mono에 기대하는 것보다는, C# 7.3의 문법이 제공되는 쪽이 더 희망적일 것이므로 시간이 좀 필요한 정도일 것입니다.




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







[최초 등록일: ]
[최종 수정일: 3/17/2021]

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

비밀번호

댓글 작성자
 




... 31  32  33  34  35  36  37  38  39  40  41  42  [43]  44  45  ...
NoWriterDateCnt.TitleFile(s)
12555정성태3/8/20218945Windows: 190. C# - 레지스트리에 등록된 DigitalProductId로부터 라이선스 키(Product Key)를 알아내는 방법파일 다운로드2
12554정성태3/8/20218774.NET Framework: 1027. 닷넷 응용 프로그램을 위한 PDB 옵션 - full, pdbonly, portable, embedded
12553정성태3/5/20219234개발 환경 구성: 548. 기존 .NET Framework 프로젝트를 .NET Core/5+ 용으로 변환해 주는 upgrade-assistant, try-convert 도구 소개 [4]
12552정성태3/5/20218501개발 환경 구성: 547. github workflow/actions에서 Visual Studio Marketplace 패키지 등록하는 방법
12551정성태3/5/20217414오류 유형: 702. 비주얼 스튜디오 - The 'CascadePackage' package did not load correctly. (2)
12550정성태3/5/20217084오류 유형: 701. Live Share 1.0.3713.0 버전을 1.0.3884.0으로 업데이트 이후 ContactServiceModelPackage 오류 발생하는 문제
12549정성태3/4/20217569오류 유형: 700. VsixPublisher를 이용한 등록 시 다양한 오류 유형 해결책
12548정성태3/4/20218338개발 환경 구성: 546. github workflow/actions에서 nuget 패키지 등록하는 방법
12547정성태3/3/20218883오류 유형: 699. 비주얼 스튜디오 - The 'CascadePackage' package did not load correctly.
12546정성태3/3/20218481개발 환경 구성: 545. github workflow/actions에서 빌드시 snk 파일 다루는 방법 - Encrypted secrets
12545정성태3/2/202111236.NET Framework: 1026. 닷넷 5에 추가된 POH (Pinned Object Heap) [10]
12544정성태2/26/202111457.NET Framework: 1025. C# - Control의 Invalidate, Update, Refresh 차이점 [2]
12543정성태2/26/20219804VS.NET IDE: 158. C# - 디자인 타임(design-time)과 런타임(runtime)의 코드 실행 구분
12542정성태2/20/202112137개발 환경 구성: 544. github repo의 Release 활성화 및 Actions를 이용한 자동화 방법 [1]
12541정성태2/18/20219367개발 환경 구성: 543. 애저듣보잡 - Github Workflow/Actions 소개
12540정성태2/17/20219686.NET Framework: 1024. C# - Win32 API에 대한 P/Invoke를 대신하는 Microsoft.Windows.CsWin32 패키지
12539정성태2/16/20219606Windows: 189. WM_TIMER의 동작 방식 개요파일 다운로드1
12538정성태2/15/20219979.NET Framework: 1023. C# - GC 힙이 아닌 Native 힙에 인스턴스 생성 - 0SuperComicLib.LowLevel 라이브러리 소개 [2]
12537정성태2/11/202111008.NET Framework: 1022. UI 요소의 접근은 반드시 그 UI를 만든 스레드에서! - 두 번째 이야기 [2]
12536정성태2/9/202110006개발 환경 구성: 542. BDP(Bandwidth-delay product)와 TCP Receive Window
12535정성태2/9/20219122개발 환경 구성: 541. Wireshark로 확인하는 LSO(Large Send Offload), RSC(Receive Segment Coalescing) 옵션
12534정성태2/8/20219753개발 환경 구성: 540. Wireshark + C/C++로 확인하는 TCP 연결에서의 closesocket 동작 [1]파일 다운로드1
12533정성태2/8/20219369개발 환경 구성: 539. Wireshark + C/C++로 확인하는 TCP 연결에서의 shutdown 동작파일 다운로드1
12532정성태2/6/20219888개발 환경 구성: 538. Wireshark + C#으로 확인하는 ReceiveBufferSize(SO_RCVBUF), SendBufferSize(SO_SNDBUF) [3]
12531정성태2/5/20218866개발 환경 구성: 537. Wireshark + C#으로 확인하는 PSH flag와 Nagle 알고리듬파일 다운로드1
12530정성태2/4/202113028개발 환경 구성: 536. Wireshark + C#으로 확인하는 TCP 통신의 Receive Window
... 31  32  33  34  35  36  37  38  39  40  41  42  [43]  44  45  ...