Quick Reference
extension은 외부 타입의 소스나 상속 구조를 바꾸지 않고 호출 문법을 추가합니다. 기존 방식은 non-nested, non-generic static class 안의 static method 첫 매개변수에 this를 붙입니다. C# 14부터는 같은 class 안에 여러 extension member를 묶는 extension block도 사용할 수 있습니다.
public static class StringExtensions
{
public static bool IsBlank(this string? value) =>
string.IsNullOrWhiteSpace(value);
}
string? name = null;
bool blank = name.IsBlank();- 호출하는 파일은 extension class가 있는 namespace를
using해야 합니다. - 실제 instance member가 있으면 extension은 선택되지 않습니다.
- nullable receiver를 허용하려면 첫 매개변수도
string?처럼 선언하고 null 처리 정책을 직접 둡니다.
선언 형태와 호출
classic extension method는 static method로도 직접 호출할 수 있으며, receiver는 첫 번째 인수로 전달됩니다. extension은 private field나 private method에 접근할 수 없고, 타입의 실제 member를 추가하거나 재정의하지도 않습니다.
public static class CollectionExtensions
{
public static bool IsEmpty<T>(this IEnumerable<T>? source) =>
source is null || !source.Any();
}
bool empty = CollectionExtensions.IsEmpty(Array.Empty<int>());
bool sameCall = Array.Empty<int>().IsEmpty();C# 14+에서는 extension block으로 같은 receiver의 method, property 등을 묶을 수 있습니다. 대상 프로젝트의 language version이 C# 14인지 확인한 뒤 사용합니다.
public static class StringExtensions
{
extension(string? value)
{
public bool IsBlank() => string.IsNullOrWhiteSpace(value);
}
}this 방식과 extension block 모두 non-nested, non-generic static class 안에 있어야 합니다. receiver 자체에는 generic type parameter와 constraint를 둘 수 있습니다.
바인딩과 값 전달
member invocation을 만났을 때 compiler는 먼저 대상 타입의 instance member를 찾고, 맞는 member가 없을 때만 extension 후보를 찾습니다. 따라서 라이브러리가 나중에 같은 시그니처의 instance method를 추가하면 기존 extension 호출의 바인딩이 바뀔 수 있습니다.
extension은 static dispatch입니다. receiver의 runtime type을 가상 호출처럼 재정의하지 않으므로, 동작을 교체해야 하는 핵심 도메인 규칙에는 interface, virtual member, composition을 사용합니다.
값 타입 receiver는 기본적으로 값 복사본으로 전달됩니다. 원본 struct를 바꿔야 하는 드문 경우에는 ref this를 명시합니다.
public static class IntExtensions
{
public static void IncrementCopy(this int value) => value++;
public static void Increment(ref this int value) => value++;
}
int count = 1;
count.IncrementCopy(); // 여전히 1
count.Increment(); // 2API 설계 기준
잘 맞는 대상은 formatting, query, 변환처럼 receiver를 첫 인수로 읽는 순수하고 작은 동작입니다. LINQ의 Where와 Select처럼 체이닝이 의미를 분명하게 하는 경우도 적합합니다.
길고 상태를 바꾸는 workflow, receiver의 private state가 필요한 규칙, 한 곳에서만 쓰는 helper는 extension으로 만들지 않습니다. 특히 object, string, 넓은 interface에 모호한 이름을 붙이면 자동완성·검색·네임스페이스 import만으로 호출 가능해지는 범위가 과도하게 넓어집니다.
null receiver를 처리하는 extension은 호출 자체는 가능하지만, null을 “빈 값”으로 취급하는지 ArgumentNullException을 던지는지는 API 계약으로 명시해야 합니다.
자주 틀리는 부분
- instance method를 extension으로 override할 수 없습니다.
- extension class namespace를 import하지 않으면 instance-style 호출 후보에 나타나지 않습니다.
- nullable annotation 없는
this string value에서 null receiver를 호출하면 warning과 runtime failure 위험이 생깁니다. - 값 타입 extension에서
ref this없이 필드를 바꿔도 원본 값은 바뀌지 않습니다.
참고 링크
1 sources