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

비밀번호

댓글 작성자
 




... 31  32  33  34  35  36  37  38  [39]  40  41  42  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
13043정성태5/3/202215402오류 유형: 807. C# - 닷넷 응용 프로그램에서 Informix DB 사용 시 오류 메시지 정리
13042정성태5/3/202218161.NET Framework: 2000. C# - 닷넷 응용 프로그램에서 Informix DB 사용 방법파일 다운로드1
13041정성태4/28/202217337개발 환경 구성: 642. Informix 데이터베이스 docker 환경 구성
13040정성태4/27/202216027VC++: 156. 비주얼 스튜디오 - Linux C/C++ 프로젝트에서 openssl 링크하는 방법
13039정성태4/27/202221545.NET Framework: 1999. C# - Playwright를 이용한 간단한 브라우저 제어 실습
13038정성태4/26/202216816오류 유형: 806. twine 실행 시 ConfigParser.ParsingError: File contains parsing errors: /root/.pypirc
13037정성태4/25/202217949.NET Framework: 1998. Azure Functions를 사용한 간단한 실습
13036정성태4/24/202218705.NET Framework: 1997. C# - nano 시간을 가져오는 방법 [2]
13035정성태4/22/202220270Windows: 204. Windows 10부터 바뀐 QueryPerformanceFrequency, QueryPerformanceCounter
13034정성태4/21/202215960.NET Framework: 1996. C# XingAPI - 주식 종목에 따른 PBR, PER, ROE, ROA 구하는 방법(t3320, t8430 예제)파일 다운로드1
13033정성태4/18/202218401.NET Framework: 1195. C# - Thread.Yield와 Thread.Sleep(0)의 차이점(?)
13032정성태4/17/202218907오류 유형: 805. Github의 50MB 파일 크기 제한 - warning: GH001: Large files detected. You may want to try Git Large File Storage
13031정성태4/15/202218783.NET Framework: 1194. C# - IdealProcessor와 ProcessorAffinity의 차이점
13030정성태4/15/202215413오류 유형: 804. 정규 표현식 오류 - Quantifier {x,y} following nothing.
13029정성태4/14/202218695Windows: 203. iisreset 후에도 이전에 설정한 전역 환경 변수가 w3wp.exe에 적용되는 문제
13028정성태4/13/202218929.NET Framework: 1193. (appsettings.json처럼) web.config의 Debug/Release에 따른 설정 적용
13027정성태4/12/202218258.NET Framework: 1192. C# - 환경 변수의 변화를 알리는 WM_SETTINGCHANGE Win32 메시지 사용법파일 다운로드1
13026정성태4/11/202218573.NET Framework: 1191. C 언어로 작성된 FFmpeg Examples의 C# 포팅 전체 소스 코드 [3]
13025정성태4/11/202218681.NET Framework: 1190. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 vaapi_encode.c, vaapi_transcode.c 예제 포팅
13024정성태4/7/202217479.NET Framework: 1189. C# - 런타임 환경에 따라 달라진 AppDomain.GetCurrentThreadId 메서드
13023정성태4/6/202218092.NET Framework: 1188. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 transcoding.c 예제 포팅 [3]
13022정성태3/31/202217313Windows: 202. 윈도우 11 업그레이드 - "PC Health Check"를 통과했지만 여전히 업그레이드가 안 되는 경우 해결책
13021정성태3/31/202218982Windows: 201. Windows - INF 파일을 이용한 장치 제거 방법
13020정성태3/30/202216027.NET Framework: 1187. RDP 접속 시 WPF UserControl의 Unloaded 이벤트 발생파일 다운로드1
13019정성태3/30/202217964.NET Framework: 1186. Win32 Message를 Code로부터 메시지 이름 자체를 텍스트로 구하고 싶다면?파일 다운로드1
13018정성태3/29/202218286.NET Framework: 1185. C# - Unsafe.AsPointer가 반환한 포인터는 pinning 상태일까요? [5]
... 31  32  33  34  35  36  37  38  [39]  40  41  42  43  44  45  ...