1. 소개
예전에는 클래스에 Extension-메서드만 추가할 수 있었어. 근데 프로퍼티(property)를 확장으로 붙이는 건 불가능했지! 예를 들어 DateTime에 IsWeekend라는 프로퍼티를 만들고 싶으면, 메서드로 구현해야 했어. 이게 좀 불편했지 — 특히 date.IsWeekend처럼 쓰고 싶은데 date.IsWeekend()처럼 괄호를 써야 하니까.
C# 14가 나오면서 드디어 소원이 이뤄졌어: 이제 프로퍼티-확장도 메서드처럼 쓸 수 있어. 기존 타입의 소스 코드를 건드리지 않고도 새로운 프로퍼티를 추가할 수 있고, 마치 원래 있던 프로퍼티처럼 쓸 수 있지!
이게 진짜 쓸모있는 경우?
- 코드를 수정할 수 없는 .NET 표준 타입이나 외부 라이브러리 쓸 때.
- view-model, UI, 계산된 뷰 등에서 편리한 "가상" 프로퍼티가 필요할 때.
- .SomeCalculatedProperty처럼 괄호 없이 예쁜 문법을 쓰고 싶을 때.
2. C# 14의 새로운 문법
C# 14에서는 확장에 this 키워드를 더 이상 안 써. 대신 extension(Type this)라는 특별한 연산자를 도입했지. 이걸로 클래스에 새로운 가상 멤버를 추가할 수 있어.
옛날 문법
public static class StringExtensions
{
public static bool IsNullOrWhiteSpace(this string str)
=> string.IsNullOrWhiteSpace(str);
}
새로운 문법
public static class Enumerable
{
extension(TSource source)
{
// 객체용 가상 멤버
}
extension(TSource)
{
// 가상 static 멤버
}
}
3. 프로퍼티-확장 문법
어떻게 생겼냐면?
예를 들어 DateTime 타입에 날짜가 주말인지 알려주는 프로퍼티를 추가하고 싶다고 해보자:
public static class DateTimeExtensions
{
public static bool IsWeekend(this DateTime date) =>
date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday;
}
근데 이건 옛날 방식의 Extension Method고, myDate.IsWeekend()처럼 괄호를 써야 해.
새로운 스타일 — Extension Property
public static class DateTimeExtensions
{
extension(DateTime source)
{
public bool IsWeekend // 프로퍼티-확장!
{
get => source.DayOfWeek == DayOfWeek.Saturday || source.DayOfWeek == DayOfWeek.Sunday;
}
}
}
이제 이렇게 쓸 수 있어:
DateTime today = DateTime.Now;
if (today.IsWeekend)
{
Console.WriteLine("더 오래 잘 수 있다!");
}
4. extension properties의 종류
읽기 전용 프로퍼티뿐만 아니라 읽기/쓰기 프로퍼티도 추가할 수 있어:
public static class DateTimeExtensions
{
extension(DateTime source)
{
public int YearFrom1900 // 프로퍼티-확장!
{
get => source.Year - 1900;
set => source.Year = value + 1900;
}
}
}
5. static 프로퍼티
새로운 문법 덕분에, 가상 프로퍼티를 객체뿐 아니라 클래스에도 추가할 수 있어! 맞아, 이제 클래스에 가상 static 프로퍼티도 붙일 수 있지. 문법은 거의 똑같아:
public static class DateTimeExtensions
{
// 새로운 문법: DateTime용 extension block (C# 14)
extension(DateTime)
{
// 사용자 현재 타임존을 저장하는 static 프로퍼티
private static TimeZoneInfo _currentTimeZone = TimeZoneInfo.Local;
// 현재 타임존에 접근하는 프로퍼티-확장
public static TimeZoneInfo CurrentTimeZone
{
get => _currentTimeZone;
set => _currentTimeZone = value ?? TimeZoneInfo.Local;
}
// 사용자 타임존으로 시간 반환
public DateTime InCurrentTimeZone
{
get => TimeZoneInfo.ConvertTime(this, CurrentTimeZone);
}
}
}
6. 예제: Dog 클래스 확장해보기!
지난 강의에서 계속 써온 강아지 앱을 더 발전시켜보자.
이제 강아지뿐 아니라 예방접종 리스트(List<DateTime>)도 있다고 해보자. 근데 Dog 클래스는 외부 라이브러리라서 코드를 못 고쳐. 우리는 이런 프로퍼티를 추가하고 싶어: 올해 필요한 예방접종을 다 했는가?
옛날 방식 (메서드-확장):
public static class DogExtensions
{
public static bool AllVaccinatedThisYear(this Dog dog)
{
return dog.Vaccinations.Any(v => v.Year == DateTime.Now.Year);
}
}
// 사용 예시:
if (myDog.AllVaccinatedThisYear())
Console.WriteLine("강아지 건강하다!");
새로운 방식 (프로퍼티-확장):
public static class DogExtensions
{
extension(Dog)
{
public bool AllVaccinatedThisYear
{
get => Vaccinations.Any(v => v.Year == DateTime.Now.Year);
}
}
}
// 사용 예시:
if (myDog.AllVaccinatedThisYear)
Console.WriteLine("강아지 건강하다!");
전체 예제
// Dog 클래스 — 예를 들어 외부 라이브러리에서 온 것:
public class Dog
{
public string Name { get; set; }
public List<DateTime> Vaccinations { get; set; } = new List<DateTime>();
}
// extension block이 있는 확장 클래스:
public static class DogExtensions
{
extension(Dog)
{
public bool AllVaccinatedThisYear
{
get => Vaccinations.Any(v => v.Year == DateTime.Now.Year);
}
}
}
// 프로그램에서:
var myDog = new Dog { Name = "드루조크" };
myDog.Vaccinations.Add(new DateTime(DateTime.Now.Year, 2, 16)); // 올해 예방접종
Console.WriteLine($"{myDog.Name}: 예방접종 했나? {(myDog.AllVaccinatedThisYear ? "네" : "아니")}");
7. 표: 메서드-확장 vs 프로퍼티-확장
| 메서드-확장 | 프로퍼티-확장 | |
|---|---|---|
| 호출 문법 | obj.Method() | obj.Property |
| 파라미터 전달 | 네 | 아니 (this obj만) |
| 쓰기 가능 | 해당 없음 | 구현되어 있으면 가능 |
| View/UI에 편리함 | 가끔 | 네 (양방향 바인딩, 람다) |
| 리플렉션에서 보임 | 아니 | 아니 |
8. 잠재적 함정과 흔한 실수
extension 프로퍼티가 안 되는 경우
- 진짜 필드를 완전히 대체할 수 없어: 객체의 private 멤버에는 접근 불가야.
- 자동 상태 변경은 불가능(예: 원본 클래스에서 값이 바뀌는 걸 바로 반영할 수 없음).
- extension property로 인터페이스나 추상 클래스 구현은 안 돼.
이름 충돌 실수
만약 원본 클래스에 네가 만든 것과 같은 이름의 프로퍼티가 생기면, "원래" 프로퍼티가 우선이야. 그래서 .NET의 public 타입을 확장할 때는 이름을 신중하게 골라야 해.
직렬화/리플렉션 문제
extension property는 "진짜" 타입 멤버가 아니라 그냥 편한 문법이야. 그래서 리플렉션이나 직렬화 도구에서는 extension 프로퍼티가 안 보일 수 있어.
GO TO FULL VERSION