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

비밀번호

댓글 작성자
 




... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...
NoWriterDateCnt.TitleFile(s)
13269정성태2/23/202315321스크립트: 46. 파이썬 - uvicorn의 콘솔 출력을 UDP로 전송
13268정성태2/22/202314796개발 환경 구성: 667. WSL 2 내부에서 열고 있는 UDP 서버를 호스트 측에서 접속하는 방법
13267정성태2/21/202316008.NET Framework: 2097. C# - 비동기 소켓 사용 시 메모리 해제가 finalizer 단계에서 발생하는 사례파일 다운로드1
13266정성태2/20/202314412오류 유형: 848. .NET Core/5+ - Process terminated. Couldn't find a valid ICU package installed on the system
13265정성태2/18/202316332.NET Framework: 2096. .NET Core/5+ - PublishSingleFile 유형에 대한 runtimeconfig.json 설정
13264정성태2/17/202318435스크립트: 45. 파이썬 - uvicorn 사용자 정의 Logger 작성
13263정성태2/16/202314268개발 환경 구성: 666. 최신 버전의 ilasm.exe/ildasm.exe 사용하는 방법
13262정성태2/15/202316400디버깅 기술: 191. dnSpy를 이용한 (소스 코드가 없는) 닷넷 응용 프로그램 디버깅 방법 [1]
13261정성태2/15/202316075Windows: 224. Visual Studio - 영문 폰트가 Fullwidth Latin Character로 바뀌는 문제 [1]
13260정성태2/14/202315831오류 유형: 847. ilasm.exe 컴파일 오류 - error : syntax error at token '-' in ... -inf
13259정성태2/14/202314087.NET Framework: 2095. C# - .NET5부터 도입된 CollectionsMarshal
13258정성태2/13/202316805오류 유형: 846. .NET Framework 4.8 Developer Pack 설치 실패 - 0x81f40001
13257정성태2/13/202316345.NET Framework: 2094. C# - Job에 Process 포함하는 방법 [2]파일 다운로드1
13256정성태2/10/202314436개발 환경 구성: 665. WSL 2의 네트워크 통신 방법 - 두 번째 이야기
13255정성태2/10/202316717오류 유형: 845. gihub - windows2022 이미지에서 .NET Framework 4.5.2 미만의 프로젝트에 대한 빌드 오류
13254정성태2/10/202315897Windows: 223. (WMI 쿼리를 위한) PowerShell 문자열 escape 처리
13253정성태2/9/202315621Windows: 222. C# - 다른 윈도우 프로그램이 실행되었음을 인식하는 방법파일 다운로드1
13252정성태2/9/202313620오류 유형: 844. ssh로 명령어 수행 시 멈춤 현상
13251정성태2/8/202315084스크립트: 44. 파이썬의 3가지 스레드 ID
13250정성태2/8/202315859오류 유형: 843. System.InvalidOperationException - Unable to configure HTTPS endpoint
13249정성태2/7/202317960오류 유형: 842. 리눅스 - You must wait longer to change your password
13248정성태2/7/202312830오류 유형: 841. 리눅스 - [사용자 계정] is not in the sudoers file. This incident will be reported.
13247정성태2/7/202316109VS.NET IDE: 180. Visual Studio - 닷넷 소스 코드 디버깅 중 "Decompile source code"가 동작하는 않는 문제
13246정성태2/6/202316129개발 환경 구성: 664. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 - 두 번째 이야기
13245정성태2/6/202314960.NET Framework: 2093. C# - PKCS#8 PEM 파일을 이용한 RSA 개인키/공개키 설정 방법파일 다운로드1
13244정성태2/5/202315824VS.NET IDE: 179. Visual Studio - External Tools에 Shell 내장 명령어 등록
... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...