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

(시리즈 글이 9개 있습니다.)
닷넷: 2342. C# 14 - (취소된 글)
; https://www.sysnet.pe.kr/2/0/13970

닷넷: 2343. C# 14 - (1) 속성 구문에서 문맥 키워드로 추가되는 field 예약어
; https://www.sysnet.pe.kr/2/0/13971

닷넷: 2346. C# 14 - (2) Span 타입과 배열 간의 암시적 형변환
; https://www.sysnet.pe.kr/2/0/13974

닷넷: 2347. C# 14 - (3) 형식 인자가 없는 제네릭 타입의 nameof 지원
; https://www.sysnet.pe.kr/2/0/13975

닷넷: 2349. C# 14 - (4) 문자열 리터럴을 utf-8 인코딩으로 저장
; https://www.sysnet.pe.kr/2/0/13977

닷넷: 2350. C# 14 - (5) 람다 매개 변수에 접근자가 있는 경우에도 타입 생략 가능
; https://www.sysnet.pe.kr/2/0/13986

닷넷: 2351. C# 14 - (6) event와 생성자에도 partial 메서드 적용
; https://www.sysnet.pe.kr/2/0/13987

닷넷: 2354. C# 14 - (7) 확장 메서드에 정적 메서드와 속성 지원을 위한 전용 구문 추가
; https://www.sysnet.pe.kr/2/0/13998

닷넷: 2355. C# 14 - (8) null 조건부 연산자 개선 - 대입문에도 사용 가능
; https://www.sysnet.pe.kr/2/0/13999




C# 14 - (7) 확장 메서드에 정적 메서드와 속성 지원을 위한 전용 구문 추가

C# 3.0에 확장 메서드가 구현된 이후, 제 기억으로는 초기부터 정적 메서드와 속성에 대해서도 확장 메서드를 지원해 달라는 요청이 있었습니다. 이에 대한 답을 C# 14에서야 해주는군요. ^^

[Proposal]: extensions #8697
; https://github.com/dotnet/csharplang/issues/8697

Extension members
; https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14#extension-members

그런데 한 가지 아쉬운 점은, 기존의 확장 메서드 문법 체계를 재사용하지 않고 아예 새로운 예약어를 도입했다는 점입니다. 그래서 다소 낯설을 수 있는데요, 그래도 문법이 어렵진 않으니 쉽게 익숙해질 수는 있을 것입니다. ^^

우선, 기존의 확장 메서드 문법은 여전히 유효한데요, 단지 C# 14부터는 새롭게 extension 예약어를 사용하는 방식도 추가된 것입니다. 예를 들어,

public static class StringExtension
{
    // C# 3.0부터 지원한 확장 메서드
    public static IPEndPoint ToEndPoint(this string value)
    {
        if (IPEndPoint.TryParse(value, out var endPoint) == false)
        {
            throw new ArgumentException($"Invalid endpoint format: {value}");
        }

        return endPoint;
    }
}

// 사용 예
// IPEndPoint ep = "192.168.100.50:8080".ToEndPoint();

위와 같이 정의한 확장 메서드를 신규 extension 예약어를 사용해 다음과 같이 대체할 수 있습니다.

/* 문법
    extension(확장할_타입 변수명)
    {
        public 반환형 메서드명(매개변수) { ... }
    }
*/

public static class StringExtension
{
    // 기존 확장 메서드 정의를 대체하는 신규 문법
    extension(string value)
    {
        public IPEndPoint ToEndPoint()
        {
            if (IPEndPoint.TryParse(value, out var endPoint) == false)
            {
                throw new ArgumentException($"Invalid endpoint format: {value}");
            }

            return endPoint;
        }
    }
}

기존에는 "this string value"로 "확장할 타입과 변수명"을 지정했지만, 이제는 "extension(string value)"와 같이 extension 괄호 안에 "확장할 타입과 변수명"을 지정하도록 바뀌었고, 메서드의 정의를 extension 블록 내부에 추가할 수 있습니다. 뭐랄까... 마치 "string value"라는 필드가 있는 클래스에서,

public class MyClass
{
    string value;
    
    public IPEndPoint ToEndPoint()
    {
        // ...[생략]...
    }
}

메서드를 정의하는 것과 비슷한 느낌마저 들 정도입니다.




신규 extension 구문에서는 속성도 정의할 수 있다고 하는데요, 사실 속성은 구현 과정에서 get/set 메서드로 바뀌는 것이므로 메서드와 구조적으로 다르지 않아 당연히 지원했어야 하던 구문입니다.

public static class IPAddressExtension
{
    extension(IPAddress address)
    {
        // 확장 메서드의 유형으로 속성(property) 정의 가능
        public uint IPv4Integer
        {
            get
            {
                if (address.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
                {
                    throw new InvalidOperationException("Only IPv4 addresses are supported.");
                }

                // Address 필드가 deprecated 되었으므로,
                // uint value = (uint)address.Address;

                return BitConverter.ToUInt32(address.GetAddressBytes(), 0);
            }
        }
    }
}

단지, 같은 속성 정의라고는 하지만 C# 컴파일러가 필드까지 생성해야만 하는 "자동 구현 속성(auto-implemented properties)"은 지원하지 않습니다.

public static class IPAddressExtension
{
    extension(IPAddress address)
    {
        // get/set 내부 코드를 구현하는 속성 정의는 지원하지만,
        public uint IPv4Integer
        {
            get { /* ...[생략]... */ }
            set { /* ...[생략]... */ }
        }

        // 자동 구현 속성(auto-implemented properties)은 지원하지 않습니다.
        public int Port { get; set; } // 컴파일 오류 - error CS9282: Extension declarations can include only methods or properties
    }
}

조금만 생각해 보면, 클래스 정의에서 결정된 인스턴스의 메모리 구조를 외부 어셈블리에서 정의할 수 있는 확장 메서드 구문에서 변경할 수는 없으므로 이해할 수 있는 부분입니다.




이번 신규 구문에서 특히 반가운 점이 있다면, 바로 인스턴스 메서드뿐만 아니라 정적 메서드 정의도 지원한다는 점입니다. 구문도 거의 비슷한데요, 인스턴스 멤버를 위한 extension과 거의 유사하지만 단지 extension 괄호 내에 타입명만 지정한다는 차이점이 하나 있습니다.

public static class IPAddressExtension
{
    // 인스턴스 메서드 정의를 위한 extension 구문 (타입명과 변수명 지정)
    extension(IPAddress address)
    {
        // ...[생략]...
    }

    public static IPAddress _minValue = new IPAddress(0x00000000);
    public static IPAddress _maxValue = new IPAddress(0xFFFFFFFF);

    // 정적 메서드 정의를 위한 extension 구문 (타입명만 지정)
    extension(IPAddress)
    {
        // 블록 내부에 정적 메서드/속성 정의 가능

        public static IPAddress MinValue => _minValue;
        public static IPAddress MaxValue => _maxValue;

        public static IPAddress FromUInt32(uint value)
        {
            return new IPAddress(value);
        }
    }
}

// 사용 예

Console.WriteLine(IPAddress.MinValue); // 출력: 0.0.0.0
Console.WriteLine(IPAddress.MaxValue); // 출력: 255.255.255.255

IPAddress addr = IPAddress.FromUInt32(845457600);
Console.WriteLine(addr); // 출력: 192.168.100.50

어떠세요? 언뜻 낯설어 보이지만 크게 어렵지 않으므로 금방 익숙해질만한 수준입니다. (제 개인적으로는 이번 C# 14에서 가장 마음에 드는 신규 문법으로 평가하고 싶습니다. ^^)




그나저나, 공식 문서에 보면 분명히 indexer에 대한 정의를 포함하고 있는데요,

// https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14#extension-members

public static class EnumerableExtension
{
    // Extension block
    extension<TSource>(IEnumerable<TSource> source) // extension members for IEnumerable<TSource>
    {
        // Extension property:
        public bool IsEmpty => !source.Any();
        // Extension indexer:
        public TSource this[int index] => source.Skip(index).First();
    }
}

하지만, 실습해 보면 컴파일 오류가 발생합니다.

IEnumerable<int> e = Enumerable.Range(1, 10);
int n = e[10]; // 컴파일 오류 - error CS0021: Cannot apply indexing with [] to an expression of type 'IEnumerable<int>'

문서만 먼저 만든 것 같기도 한데요, 어쨌든 C# 14 공식 출시에는 포함하지 않을까 싶습니다. 이와 관련해 문서에서는 다음의 순서로 기능 구현을 할 예정이라는 언급도 있는데,

  1. Properties and methods (instance and static)
  2. Operators
  3. Indexers (instance and static, may be done opportunistically at an earlier point)
  4. Anything else

순서상으로 보면 오히려 indexer보다 연산자 재정의가 먼저군요. ^^; 아마도 현재의 분위기로 보면 연산자 재정의는 C# 15에서나 지원하지 않을까 싶습니다.

마지막으로, 기존 문법으로 정의하든 신규 문법으로 정의하든 컴파일된 확장 메서드의 시그니처는 동일하기 때문에 저 2개의 메서드를 함께 정의할 수는 없습니다.

public static class StringExtension
{
    // 기존 확장 메서드 정의
    public static IPEndPoint ToEndPoint(this string value)
    {
        if (IPEndPoint.TryParse(value, out var endPoint) == false)
        {
            throw new ArgumentException($"Invalid endpoint format: {value}");
        }

        return endPoint;
    }

    extension(string value)
    {
        // 동시에 신규 확장 메서드 정의를 정의하면?
        // 컴파일 오류 - error CS0111: Type 'StringExtension' already defines a member called 'ToEndPoint' with the same parameter types
        public IPEndPoint ToEndPoint()
        {
            if (IPEndPoint.TryParse(value, out var endPoint) == false)
            {
                throw new ArgumentException($"Invalid endpoint format: {value}");
            }

            return endPoint;
        }
    }
}




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







[최초 등록일: ]
[최종 수정일: 8/14/2025]

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

비밀번호

댓글 작성자
 




... 106  107  108  109  [110]  111  112  113  114  115  116  117  118  119  120  ...
NoWriterDateCnt.TitleFile(s)
11251정성태7/13/201720337디버깅 기술: 88. windbg 분석 - webengine4.dll의 MgdExplicitFlush에서 발생한 System.AccessViolationException의 crash 문제 (2)
11250정성태7/13/201724142디버깅 기술: 87. windbg 분석 - webengine4.dll의 MgdExplicitFlush에서 발생한 System.AccessViolationException의 crash 문제 [1]
11249정성태7/12/201721518오류 유형: 410. LoadLibrary("[...].dll") failed - The specified procedure could not be found.
11248정성태7/12/201728129오류 유형: 409. pip install pefile - 'cp949' codec can't decode byte 0xe2 in position 208687: illegal multibyte sequence
11247정성태7/12/201722505오류 유형: 408. SqlConnection 객체 생성 시 무한 대기 문제파일 다운로드1
11246정성태7/11/201719747VS.NET IDE: 118. Visual Studio - 다중 폴더에 포함된 파일들에 대한 "Copy to Output Directory"를 한 번에 설정하는 방법
11245정성태7/10/201725584개발 환경 구성: 321. Visual Studio Emulator for Android 소개 [2]
11244정성태7/10/201726661오류 유형: 407. Visual Studio에서 ASP.NET Core 실행할 때 dotnet.exe 프로세스의 -532462766 오류 발생 [1]
11243정성태7/10/201723495.NET Framework: 666. dotnet.exe - 윈도우 운영체제에서의 .NET Core 버전 찾기 규칙
11242정성태7/8/201722252제니퍼 .NET: 27. 제니퍼 닷넷 적용 사례 (7) - 노후된 스토리지 장비로 인한 웹 서비스 Hang (멈춤) 현상
11241정성태7/8/201720756오류 유형: 406. Xamarin 빌드 에러 XA5209, APT0000
11240정성태7/7/201725297.NET Framework: 665. ClickOnce를 웹 브라우저를 이용하지 않고 쿼리 문자열을 전달하면서 실행하는 방법 [3]파일 다운로드1
11239정성태7/6/201725129.NET Framework: 664. Protocol Handler - 웹 브라우저에서 데스크톱 응용 프로그램을 실행하는 방법 [5]파일 다운로드1
11238정성태7/6/201722601오류 유형: 405. NT 서비스 시작 시 "Error 1067: The process terminated unexpectedly." 오류 발생 [2]
11237정성태7/5/201724476.NET Framework: 663. C# - PDB 파일 경로를 PE 파일로부터 얻는 방법파일 다운로드1
11236정성태7/4/201728243.NET Framework: 662. C# - VHD/VHDX 가상 디스크를 마운트하지 않고 파일을 복사하는 방법파일 다운로드1
11235정성태6/29/201722726Math: 20. Matlab/Octave로 Gram-Schmidt 정규 직교 집합 구하는 방법
11234정성태6/29/201720761오류 유형: 404. SharePoint 2013 설치 과정에서 "The username is invalid The account must be a valid domain account" 오류 발생
11233정성태6/28/201720642오류 유형: 403. SharePoint Server 2013을 Windows Server 2016에 설치할 때 .NET 4.5 설치 오류 발생
11232정성태6/28/201721485Windows: 144. Windows Server 2016에 Windows Identity Extensions을 설치하는 방법
11231정성태6/28/201720443디버깅 기술: 86. windbg의 mscordacwks DLL 로드 문제 - 세 번째 이야기 [1]
11230정성태6/28/201720063제니퍼 .NET: 26. 제니퍼 닷넷 적용 사례 (6) - 잦은 Recycle 문제
11229정성태6/27/201721844오류 유형: 402. Windows Server Backup 관리 콘솔이 없어진 경우
11228정성태6/26/201718482개발 환경 구성: 320. Visual Basic .NET 프로젝트에서 내장 Manifest 자원을 EXE 파일로부터 제거하는 방법파일 다운로드1
11227정성태6/19/201728038개발 환경 구성: 319. windbg에서 python 스크립트 실행하는 방법 - pykd [6]
11226정성태6/19/201718092오류 유형: 401. Microsoft Edge를 실행했는데 입력 반응이 없는 경우
... 106  107  108  109  [110]  111  112  113  114  115  116  117  118  119  120  ...