CodeGym /Các khóa học /C# SELF /Extension Members: thuộc tính mở rộng

Extension Members: thuộc tính mở rộng

C# SELF
Mức độ , Bài học
Có sẵn

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
    }
}
Cú pháp mới extension members (C# 14+)

Lưu ý! Cú pháp extension mới này chỉ chạy trên C# 14+.NET 10+. Nếu bạn dùng .NET 9 thì nó không chạy đâu. Nhưng nếu vẫn muốn thử mà .NET 10 chưa ra, thì cài bản .NET 10 preview 5+ nhé.
Ghi chú! Khóa học C# của tụi mình dùng toàn tính năng mới nhất của C# 14+, kể cả mấy cái chưa chính thức ra mắt. Mùa thu 2025 sẽ có .NET 10, và khi bạn học xong khóa này, bạn sẽ cực kỳ pro về C#! 😎

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; 
        }        
    }
}
Read-write extension property
Lưu ý: Với read-write extension property bạn phải hiểu là nó thay đổi chính object, nhưng không thể lưu giá trị “mới” ở đâu đó riêng biệt (kiểu auto field). Nếu muốn thêm property mà object gốc không có, bạn sẽ cần storage riêng (ví dụ dictionary), mà cái này thì vượt ngoài phạm vi cơ bản rồi.

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ố 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.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Tạo thuộc tính mở rộng để kiểm tra năm nhuận
Tạo thuộc tính mở rộng để kiểm tra năm nhuận
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Tạo thuộc tính mở rộng cho chuỗi
Tạo thuộc tính mở rộng cho chuỗi
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION