1. Giới thiệu
Trước đây chỉ có thể thêm Extension-method cho class thôi. Còn muốn thêm property (property) như mở rộng class thì không được! Tức là, nếu bạn muốn bất kỳ DateTime nào có property IsWeekend, bạn phải làm qua method, mà như vậy thì không tiện lắm — nhất là khi muốn cú pháp kiểu date.IsWeekend thay vì date.IsWeekend().
Khi C# 14 ra mắt thì ước mơ thành hiện thực: giờ có thể viết property mở rộng kiểu hơi giống method. Nó cho phép thêm property mới vào kiểu đã có mà không cần sửa code gốc, và dùng như property bình thường luôn!
Khi nào thực sự hữu ích?
- Cho các kiểu chuẩn .NET và thư viện ngoài mà bạn không thể sửa code gốc.
- Cho các property “ảo” tiện lợi trong view-model, UI, view tính toán.
- Khi muốn cú pháp đẹp kiểu .SomeCalculatedProperty không cần ngoặc.
2. Cú pháp mới trong C# 14
Trong C# 14 người ta bỏ từ khóa this cho extension. Thay vào đó là operator đặc biệt extension(Type this). Nhờ nó bạn có thể thêm các thành phần ảo vào class.
Cú pháp cũ
public static class StringExtensions
{
public static bool IsNullOrWhiteSpace(this string str)
=> string.IsNullOrWhiteSpace(str);
}
Cú pháp mới
public static class Enumerable
{
extension(TSource source)
{
// Thành viên ảo cho object
}
extension(TSource)
{
// Thành viên static ảo
}
}
3. Cú pháp property mở rộng
Trông nó như nào?
Giả sử bạn muốn thêm vào kiểu DateTime một property trả về ngày đó có phải cuối tuần không:
public static class DateTimeExtensions
{
public static bool IsWeekend(this DateTime date) =>
date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday;
}
Nhưng đây là kiểu Extention Method cũ, gọi kiểu: myDate.IsWeekend()
Kiểu mới — Extension Property
public static class DateTimeExtensions
{
extension(DateTime source)
{
public bool IsWeekend // property mở rộng!
{
get => source.DayOfWeek == DayOfWeek.Saturday || source.DayOfWeek == DayOfWeek.Sunday;
}
}
}
Giờ bạn có thể viết như này:
DateTime today = DateTime.Now;
if (today.IsWeekend)
{
Console.WriteLine("Có thể ngủ thêm!");
}
4. Có những loại extension property nào
Bạn có thể thêm không chỉ property chỉ đọc (read-only), mà còn cả read-write:
public static class DateTimeExtensions
{
extension(DateTime source)
{
public int YearFrom1900 // property mở rộng!
{
get => source.Year - 1900;
set => source.Year = value + 1900;
}
}
}
5. Thuộc tính static
Nhờ cú pháp mới, bạn có thể thêm property ảo không chỉ cho object mà còn cho class! Đúng rồi, giờ mình có thể thêm property static ảo cho class. Cú pháp gần như giống nhau:
public static class DateTimeExtensions
{
// Cú pháp mới: extension block cho DateTime (C# 14)
extension(DateTime)
{
// Property static để lưu timezone hiện tại của user
private static TimeZoneInfo _currentTimeZone = TimeZoneInfo.Local;
// Property mở rộng để truy cập timezone hiện tại
public static TimeZoneInfo CurrentTimeZone
{
get => _currentTimeZone;
set => _currentTimeZone = value ?? TimeZoneInfo.Local;
}
// Trả về thời gian theo timezone user
public DateTime InCurrentTimeZone
{
get => TimeZoneInfo.ConvertTime(this, CurrentTimeZone);
}
}
}
6. Ví dụ: mở rộng class Dog!
Tiếp tục phát triển app về chó mà tụi mình đã kéo dài từ bài trước nhé.
Giả sử giờ không chỉ có chó mà còn có danh sách tiêm chủng (List<DateTime>), nhưng class Dog là từ thư viện ngoài, bạn không sửa code được. Mình muốn thêm property: Đã tiêm đủ vaccine cần thiết trong năm nay chưa?
Cách cũ (method mở rộng):
public static class DogExtensions
{
public static bool AllVaccinatedThisYear(this Dog dog)
{
return dog.Vaccinations.Any(v => v.Year == DateTime.Now.Year);
}
}
// Sử dụng:
if (myDog.AllVaccinatedThisYear())
Console.WriteLine("Chó khỏe mạnh!");
Cách mới (property mở rộng):
public static class DogExtensions
{
extension(Dog)
{
public bool AllVaccinatedThisYear
{
get => Vaccinations.Any(v => v.Year == DateTime.Now.Year);
}
}
}
// Sử dụng:
if (myDog.AllVaccinatedThisYear)
Console.WriteLine("Chó khỏe mạnh!");
Ví dụ đầy đủ
// Class Dog — ví dụ từ thư viện ngoài:
public class Dog
{
public string Name { get; set; }
public List<DateTime> Vaccinations { get; set; } = new List<DateTime>();
}
// Class extension với extension block:
public static class DogExtensions
{
extension(Dog)
{
public bool AllVaccinatedThisYear
{
get => Vaccinations.Any(v => v.Year == DateTime.Now.Year);
}
}
}
// Trong chương trình:
var myDog = new Dog { Name = "Druzhok" };
myDog.Vaccinations.Add(new DateTime(DateTime.Now.Year, 2, 16)); // tiêm năm nay
Console.WriteLine($"{myDog.Name}: đã tiêm vaccine? {(myDog.AllVaccinatedThisYear ? "Có" : "Không")}");
7. Bảng: Extension-method vs Extension-property
| Extension-method | Extension-property | |
|---|---|---|
| Cú pháp gọi | obj.Method() | obj.Property |
| Truyền tham số | Có | Không (chỉ this obj) |
| Có thể ghi | Không áp dụng | Chỉ khi có implement |
| Tiện cho View/UI | Thỉnh thoảng | Có (binding 2 chiều, lambda) |
| Thấy được qua reflection | Không | Không |
8. Những bẫy tiềm ẩn và lỗi thường gặp
Khi extension-property không giúp được
- Chúng không thể thay thế hoàn toàn field thật: không truy cập được thành viên private của object.
- Không thể thêm tự động thay đổi trạng thái object (ví dụ không thể theo dõi thay đổi giá trị trong class gốc trực tiếp).
- Không dùng extension property để implement interface hay abstract class được.
Lỗi trùng tên
Nếu class gốc sau này có property trùng tên với bạn, thì property “gốc” sẽ được dùng, không phải extension của bạn. Nên hãy cẩn thận khi đặt tên, nhất là khi làm với kiểu public của .NET.
Vấn đề với serialization/reflection
Extension property — không phải thành viên “thật” của kiểu, chỉ là cú pháp tiện thôi. Nên nếu dùng reflection hay serialization, property mở rộng có thể sẽ không thấy được.
GO TO FULL VERSION