CodeGym /행동 /C# SELF /Extension Members: 프로퍼티

Extension Members: 프로퍼티

C# SELF
레벨 18 , 레슨 3
사용 가능

1. 소개

예전에는 클래스에 Extension-메서드만 추가할 수 있었어. 근데 프로퍼티(property)를 확장으로 붙이는 건 불가능했지! 예를 들어 DateTimeIsWeekend라는 프로퍼티를 만들고 싶으면, 메서드로 구현해야 했어. 이게 좀 불편했지 — 특히 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 멤버
    }
}
extension members의 새로운 문법 (C# 14+)

중요! 이 새로운 extension 문법은 C# 14+.NET 10+에서만 동작해. 만약 .NET 9를 쓰고 있다면 안 돼. 그래도 써보고 싶으면, .NET 10이 아직 정식 출시가 아니더라도 .NET 10 preview 5+를 설치해봐.
참고! 우리 C# 강의는 C# 14+의 최신 기능까지 다루고 있어. 아직 공식 출시 안 된 기능도 포함이야. 2025년 가을에 .NET 10이 나오면, 너희는 이미 최신 C# 지식을 갖고 있을 거야! 😎

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; 
        }        
    }
}
읽기/쓰기 extension property
중요: 읽기/쓰기 extension property는 실제로 객체 자체를 바꾸는 거야. 근데 "새로운" 값을 따로 저장할 수는 없어(자동 필드처럼). 만약 객체에 실제로 없는 프로퍼티를 추가하고 싶으면, 별도의 저장소(예: 딕셔너리)가 필요해. 이건 기본 사용법을 벗어나니까 참고만 해.

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 프로퍼티가 안 보일 수 있어.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION