JavaRush /Курси /C# SELF /Extension members: властивості

Extension members: властивості

C# SELF
Рівень 18 , Лекція 3
Відкрита

1. Вступ

Раніше до класів можна було додавати лише методи-розширення. А от додати властивість (property) як розширення класу було неможливо! Тобто, якщо ви хотіли, щоб у будь-якого DateTime зʼявилася властивість IsWeekend, доводилося робити це через метод, що не завжди зручно — особливо якщо хочеться синтаксис date.IsWeekend замість date.IsWeekend().

Із виходом C# 14 це стало можливим: тепер можна оголошувати властивості-розширення майже так само, як методи. Вони дають змогу додати нову властивість до наявного типу, не змінюючи його початкового коду, і використовувати її так, ніби це звичайна властивість!

Де це справді корисно?

  • Для стандартних типів .NET і сторонніх бібліотек, код яких змінити не можна.
  • Для зручних «віртуальних» властивостей у view‑моделях, 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)
    {
        // Віртуальні статичні члени
    }
}
Новий синтаксис extension members (C# 14+)

Важливо! Цей новий extension-синтаксис працює тільки в C# 14+ і .NET 10+. Якщо ви користуєтеся .NET 9, то він не працюватиме. Якщо дуже хочеться спробувати, а .NET 10 ще не вийшов, установіть версію .NET 10 preview 7+
Примітка! Наш курс з 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;
}

Але це — старий варіант методу-розширення; виклик виглядає так: myDate.IsWeekend()

Новий стиль — властивість-розширення


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. Які бувають властивості-розширення

Можна додати не лише властивості тільки для читання, а й властивості для читання й запису:


public static class DateTimeExtensions
{
    extension(DateTime source)
    {
        public int YearFrom1900 // властивість-розширення!
        {
            get => source.Year - 1900;
            set => source.Year = value + 1900; 
        }        
    }
}
Властивість-розширення з читанням і записом
Важливо: Для властивостей-розширень із читанням і записом варто розуміти, що вони змінюють сам об’єкт, але не можуть зберігати «нове» значення десь окремо (як автоматичне поле). Якщо хочете додати властивість, фізично відсутню в об’єкті, знадобиться окреме сховище (наприклад, словник), що виходить за межі базового використання.

5. Статичні властивості

Завдяки новому синтаксису «віртуальні» властивості можна додавати не лише об’єкту, а й класу. Так, ви все правильно зрозуміли: тепер можна додавати «віртуальні» статичні властивості до класу. Синтаксис майже такий самий:


public static class DateTimeExtensions
{
    // Новий синтаксис: блок extension для DateTime (C# 14)
    extension(DateTime)
    {
        // Статична властивість для зберігання поточного часового поясу користувача
        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:
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. Потенційні підводні камені та типові помилки

Коли властивості-розширення не допоможуть

  • Вони не можуть повністю замінити справжні поля: у них немає доступу до приватних членів об’єкта.
  • Не можна додати автоматичну зміну стану об’єкта (наприклад, не вдасться відстежувати зміну значення в початковому класі безпосередньо).
  • Не можна використовувати властивість-розширення для реалізації інтерфейсу чи абстрактного класу.

Помилка з іменами

Якщо в оригінальному класі зʼявиться властивість із таким самим імʼям, як у вас, використовуватиметься «рідна» властивість, а не ваше розширення. Тому будьте уважні з вибором імен, особливо коли працюєте з публічними типами у .NET.

Проблеми з серіалізацією/рефлексією

Властивість-розширення — це не «реальний» член типу, а просто зручний синтаксичний цукор. Тому, якщо десь використовується рефлексія чи серіалізація, властивість-розширення може бути невидимою для таких інструментів.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ