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)
{
// Віртуальні статичні члени
}
}
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.
Проблеми з серіалізацією/рефлексією
Властивість-розширення — це не «реальний» член типу, а просто зручний синтаксичний цукор. Тому, якщо десь використовується рефлексія чи серіалізація, властивість-розширення може бути невидимою для таких інструментів.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ