본문으로 건너뛰기
C# 14 확장 블록 완벽 가이드: Extension Blocks로 더 깔끔한 코드 만들기
  1. 포스트/
  2. 인사이트/

C# 14 확장 블록 완벽 가이드: Extension Blocks로 더 깔끔한 코드 만들기

목차

그동안 C#에서 프로퍼티나 정적 멤버는 왜 확장할 수 없을까 고민하셨나요? 2025년 11월 출시된 C# 14와 .NET 10이 그 해답을 가져왔어요. 이제 ‘Extension Blocks’를 통해 여러분의 코드가 얼마나 더 직관적으로 변할 수 있는지 핵심만 짚어 드릴게요.


시작하기 전에
#

오늘 소개해 드릴 예제들은 2025년 11월 11일에 공식 출시된 .NET 10C# 14 환경이 필요해요. 아직 설치 전이라면 공식 홈페이지에서 내려받을 수 있어요.

준비가 되셨다면, 이제 본격적으로 들어가 볼게요!


왜 C# 14 확장 블록이 필요할까요?
#

기존 방식도 충분히 훌륭했지만, 몇 가지 가려운 부분이 있었어요.

  • 메서드만 가능: 프로퍼티나 연산자는 확장할 수 없었어요.
  • 반복되는 코드: 같은 타입을 확장할 때마다 매번 this TypeName parameter를 써줘야 했죠.
  • 정적 확장 불가: 인스턴스가 아닌 타입 자체에 정적 멤버를 추가할 수 없었어요.

예를 들어, 리스트가 비었는지 확인할 때 myList.IsEmpty()처럼 괄호를 붙이는 게 가끔은 어색하게 느껴질 때가 있더라고요. 그냥 myList.IsEmpty처럼 프로퍼티로 쓰고 싶다는 생각, 다들 한 번쯤 해보셨을 거예요.


C# 14 확장 블록(Extension Blocks) 핵심 개념과 문법
#

C# 14의 확장 블록은 이 모든 고민을 해결해 줘요. 이제 메서드뿐만 아니라 프로퍼티, 연산자, 정적 멤버까지 한 번에 정의할 수 있답니다.

예제로 보는 변화
#

문자열이 비었는지 확인하는 코드를 새 문법으로 바꿔볼게요.

// 새로운 방식: 확장 블록 사용
public static class EnumerableExtensions
{
    extension(IEnumerable source)
    {
        // 이제 메서드가 아니라 '프로퍼티'로 정의할 수 있어요!
        public bool IsEmpty => !source.Any();
    }
}

문법이 꽤 직관적이죠? extension 키워드 뒤에 확장할 타입과 이름을 적고, 중괄호 안에 원하는 멤버를 넣으면 끝이에요. 호출할 때도 numbers.IsEmpty처럼 자연스럽게 프로퍼티로 접근할 수 있어 훨씬 보기 좋아요.

C# 14 확장 메서드의 진화


실전 예제: 문자열 유틸리티 및 프로퍼티 확장
#

실제로 유용하게 쓸 수 있는 문자열 유틸리티 예제를 만들어봤어요.

public static class StringExtensions
{
    extension(string str)
    {
        public bool IsEmpty => string.IsNullOrEmpty(str);
        public bool IsValidEmail => str.Contains("@") && str.Contains(".");

        public string Truncate(int maxLength)
        {
            if (str.Length <= maxLength) return str;
            return str.Substring(0, maxLength) + "...";
        }
    }
}

이제 email.IsEmptyemail.IsValidEmail처럼 쓸 수 있어요. 괄호가 사라지니까 코드가 읽기 훨씬 편해지더라고요.


정적 확장 (Static Extensions)
#

이게 정말 대박이에요. 인스턴스가 아니라 타입 자체에 멤버를 추가할 수 있거든요. 파라미터 이름을 생략하면 정적 확장이 됩니다.

public static class ListExtensions
{
    extension(List)
    {
        // 타입 자체에서 호출하는 정적 프로퍼티
        public static List Empty => new List();
    }
}

// 사용 예시
var myNewList = List.Empty;

기존에는 불가능했던 방식이라, 팩토리 메서드나 유틸리티 함수를 만들 때 정말 유용할 것 같아요.


확장 블록의 3가지 제약 사항과 한계
#

물론 만능은 아니에요. 몇 가지 기억해 둘 포인트가 있어요.

필드 추가 불가
#

확장 블록은 기존 타입에 ‘기능’을 붙이는 것이지, 새로운 ‘데이터 공간’을 만드는 게 아니에요. 그래서 상태를 저장해야 하는 필드나 자동 구현 프로퍼티는 쓸 수 없답니다.

extension(User user)
{
    // ❌ 오류: 확장 블록 내부에서는 필드를 선언할 수 없어요.
    private int _accessCount; 

    // ❌ 오류: 필드가 필요한 자동 구현 프로퍼티도 안 돼요.
    public string Nickname { get; set; }

    // ✅ 계산된 프로퍼티(로직만 있는 경우)는 가능해요!
    public string FullName => $"{user.FirstName} {user.LastName}";
}

기존 멤버 우선
#

만약 확장하려는 타입에 이미 같은 이름의 메서드나 프로퍼티가 있다면 어떻게 될까요? C#은 언제나 원본 타입의 멤버를 우선해서 호출해요. 확장이 원본을 덮어쓰거나 가로챌 수는 없다는 점을 꼭 기억해야 해요.

extension(string str)
{
    // ⚠️ 원본 string에 이미 Length 프로퍼티가 있죠?
    // 이 코드는 에러는 안 나지만, str.Length를 호출하면 항상 원본 값이 나옵니다.
    public int Length => 999; 
}

string name = "Gemini";
Console.WriteLine(name.Length); // 결과는 999가 아니라 '6'이 나와요!

제네릭 제약
#

확장 블록에서 제네릭(T)을 쓸 때는, 그 타입 파라미터가 반드시 확장 대상이 되는 타입(Receiver)에 포함되어 있어야 해요.

// ✅ T가 List<T> 안에 포함되어 있으므로 가능해요.
extension(List<T> list) 
{
    public void PrintAll() => list.ForEach(Console.WriteLine);
}

// ❌ 오류: T2는 확장 대상인 List<T1> 어디에도 속해있지 않아요.
extension(List<T1> list) 
{
    public void DoSomething<T2>(T2 extra) { /* ... */ }
}

요약하자면 이래요!
#

“확장 블록은 타입에 새로운 시각(View)을 제공하는 것이지, 타입의 설계도 자체를 수정하는 것은 아니에요.”

이 세 가지만 기억해도 확장 블록을 쓰면서 겪을 시행착오를 훨씬 줄일 수 있을 거예요.


마무리하며
#

C# 14의 확장 블록은 우리가 코드를 더 ‘C#답게’ 표현할 수 있도록 도와주는 아주 반가운 변화예요. 기존 방식과 섞어서 쓸 수도 있으니, 당장 모든 코드를 바꿀 필요도 없죠.

새로운 .NET 10 환경에서 작은 유틸리티부터 하나씩 적용해 보시는 건 어떨까요? 코드가 한결 가벼워지는 걸 느끼실 수 있을 거예요.

최근 글

Studio Rainshelter
작성자
Studio Rainshelter

관련 글