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

비밀번호

댓글 작성자
 




... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1857정성태2/16/201525227.NET Framework: 494. 값(struct) 형식의 제네릭(Generic) 타입이 박싱되는 경우의 메타데이터 토큰 값파일 다운로드1
1856정성태2/15/201523848.NET Framework: 493. TypeRef 메타테이블에 등록되는 타입의 조건파일 다운로드1
1855정성태2/10/201523191개발 환경 구성: 256. WebDAV Redirector - Sysinternals 폴더 연결 시 "The network path was not found" 오류 해결 방법
1854정성태2/10/201524123Windows: 104. 폴더는 삭제할 수 없지만, 그 하위 폴더/파일은 생성/삭제/변경하는 보안 설정
1853정성태2/6/201554811웹: 29. 여신금융협회 웹 사이트의 "Netscape 6.0은 지원하지 않습니다." 오류 메시지 [5]
1852정성태2/5/201525663.NET Framework: 492. .NET CLR Memory 성능 카운터의 의미파일 다운로드1
1851정성태2/5/201526257VC++: 88. 하룻밤의 꿈 - 인텔 하스웰의 TSX Instruction 지원 [3]
1850정성태2/4/201547073Windows: 103. 작업 관리자에서의 "Commit size"가 가리키는 메모리의 의미 [4]
1849정성태2/4/201526083기타: 51. DropBox의 CPU 100% 현상 [1]파일 다운로드1
1848정성태2/4/201522228.NET Framework: 491. 닷넷 Generic 타입의 메타 데이터 토큰 값 알아내는 방법 [2]
1847정성태2/3/201525575기타: 50. C# - 윈도우에서 dropbox 동기화 폴더 경로 및 종료하는 방법
1846정성태2/2/201534591Windows: 102. 제어판의 프로그램 추가/삭제 항목을 수동으로 실행하고 싶다면? [1]
1845정성태1/26/201536003Windows: 101. 제어판의 "Windows 자격 증명 관리(Manage your credentials)"를 금지시키는 방법
1844정성태1/26/201533093오류 유형: 269. USB 메모리의 용량이 비정상적으로 보여진다면? [7]
1843정성태1/24/201524441VC++: 87. 무시할 수 없는 Visual C++ 런타임 함수 성능
1842정성태1/23/201548114개발 환경 구성: 255. 노트북 키보드에 없는 BREAK 키를 다른 키로 대체하는 방법
1841정성태1/21/201521934오류 유형: 268. Win32 핸들 관련 CLR4 보안 오류 사례
1840정성태1/8/201530249오류 유형: 267. Visual Studio - CodeLens 사용 시 CPU 100% 현상
1839정성태1/5/201522652디버깅 기술: 69. windbg 분석 사례 - cpu 100% 현상 (2)
1838정성태1/4/201543146기타: 49. 윈도우 내레이터(Narrator) 기능 끄는 방법(윈도우에 파란색의 굵은 테두리 선이 나타난다면?) [4]
1837정성태1/4/201529372디버깅 기술: 68. windbg 분석 사례 - 메모리 부족 [1]
1836정성태1/4/201529238디버깅 기술: 67. windbg - 덤프 파일과 handle 정보
1835정성태1/3/201529917개발 환경 구성: 254. SQL 서버 역시 SSL 3.0/TLS 1.0만을 지원하는 듯!
1834정성태1/3/201554582개발 환경 구성: 253. TLS 1.2를 적용한 IIS 웹 사이트 구성
1833정성태1/3/201530928.NET Framework: 490. System.Data.SqlClient는 SSL 3.0/TLS 1.0만 지원하는 듯! [3]
1832정성태1/2/201522953오류 유형: 266. Azure에 응용 프로그램 게시 중 로그인 오류
... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...