CodeGym /課程 /C# SELF /Extension Members:屬性

Extension Members:屬性

C# SELF
等級 18 , 課堂 3
開放

1. 前言

以前你只能幫 class 加 Extension-методы。但想加一個屬性(property)當作 class 的擴充是不行的!也就是說,如果你想讓任何 DateTime 都有個 IsWeekend 屬性,只能用 method 來做,這其實有點麻煩——尤其你想要 date.IsWeekend 這種語法,而不是 date.IsWeekend()

隨著 C# 14 出來,這個夢想成真啦:現在你可以用一種跟方法有點像的方式寫 extension properties。這讓你可以幫現有型別加新屬性,不用改原始碼,而且用起來就像一般屬性一樣!

什麼時候真的有用?

  • 對 .NET 標準型別或外部 library,原始碼你動不了。
  • 在 view-model、UI、計算型 view 這種地方加「虛擬」屬性很方便。
  • 想要漂亮語法 .SomeCalculatedProperty,不用多餘括號。

2. C# 14 的新語法

C# 14,官方決定 extension 不再用 this 關鍵字。改成用一個特別的 operator:extension(Type this)。這樣你就能幫 class 加新的虛擬成員。

舊語法

public static class StringExtensions
{
    public static bool IsNullOrWhiteSpace(this string str)
        => string.IsNullOrWhiteSpace(str);
}

新語法


public static class Enumerable
{
    extension(TSource source)
    {
        // 虛擬成員 for 物件
    }

    extension(TSource)
    {
        // 虛擬 static 成員
    }
}
新 extension members 語法(C# 14+)

注意! 這個新的 extension 語法只在 C# 14+.NET 10+ 才能用。如果你還在用 .NET 9,它 不能用。但如果你想搶先玩玩看,而 .NET 10 還沒正式出,記得裝 .NET 10 preview 5+
小提醒! 我們這門 C# 課程會用到 C# 14+ 最新語言功能,甚至有些還沒正式 release。2025 年秋天 .NET 10 就會出,到時你學完課程,C# 最潮新知識都在你腦袋裡!😎

3. extension properties 的語法

長什麼樣?

假設你想幫 DateTime 型別加一個屬性,回傳這天是不是週末:

public static class DateTimeExtensions
{
    public static bool IsWeekend(this DateTime date) => 
        date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday;
}

但這是舊的 Extention Method 寫法,呼叫時要:myDate.IsWeekend()

新寫法——Extension Property


public static class DateTimeExtensions
{
    extension(DateTime source)
    {
        public bool IsWeekend // extension property!
        {
            get => source.DayOfWeek == DayOfWeek.Saturday || source.DayOfWeek == DayOfWeek.Sunday;
        }        
    }
}

現在你可以這樣寫:

DateTime today = DateTime.Now;
if (today.IsWeekend)
{
    Console.WriteLine("可以睡晚一點!");
}

4. extension properties 有哪些型態

不只可以加 read-only 屬性,也能加 read-write:


public static class DateTimeExtensions
{
    extension(DateTime source)
    {
        public int YearFrom1900 // extension property!
        {
            get => source.Year - 1900;
            set => source.Year = value + 1900; 
        }        
    }
}
Read-write extension property
注意: 用 read-write extension properties 時要知道,它們會改到原本的物件,但不能像自動欄位那樣自己存「新」值。如果你想加一個物件本身沒有的屬性,得自己搞 storage(像 dictionary),這就超出基本用法囉。

5. 靜態屬性

有了新語法,虛擬屬性不只可以加在物件,也能加在 class!沒錯,你沒看錯,現在可以幫 class 加虛擬 static 屬性。語法幾乎一樣:


public static class DateTimeExtensions
{
    // 新語法:DateTime 的 extension block(C# 14)
    extension(DateTime)
    {
        // static 屬性,存目前使用者時區
        private static TimeZoneInfo _currentTimeZone = TimeZoneInfo.Local;

        // extension property,取得目前時區
        public static TimeZoneInfo CurrentTimeZone
        {
            get => _currentTimeZone;
            set => _currentTimeZone = value ?? TimeZoneInfo.Local;
        }

        // 回傳轉換到使用者時區的時間
        public DateTime InCurrentTimeZone
        {
            get => TimeZoneInfo.ConvertTime(this, CurrentTimeZone);
        }
    }
}

6. 範例:擴充 Dog 類別!

讓我們繼續進化我們的狗狗 app,這個例子我們從前幾堂課一路帶過來的。

假設現在不只狗狗,還有牠們的疫苗接種紀錄(List<DateTime>),但 Dog class 是外部 library 的,你不能改它的 code。我們想加一個屬性:今年所有必要疫苗都打了嗎?

舊方法(method extension):

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("狗狗很健康!");

新方法(extension property):


public static class DogExtensions
{
    extension(Dog)
    {
        public bool AllVaccinatedThisYear
        {
            get => Vaccinations.Any(v => v.Year == DateTime.Now.Year);
        }
    }
}

// 用法:
if (myDog.AllVaccinatedThisYear)
    Console.WriteLine("狗狗很健康!");

完整範例

// Dog 類別——比如外部 library 來的:
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 有時候 很適合(雙向綁定、lambda)
反射可見性 不會 不會

8. 潛在陷阱與常見錯誤

什麼時候 extension 屬性幫不上忙

  • 它們不能完全取代真正的欄位:沒辦法存取物件的 private 成員。
  • 不能加 自動 狀態變更(比如不能直接追蹤原本 class 的值變化)。
  • 不能用 extension property 來實作 interface 或 abstract class。

命名衝突

如果原本 class 之後加了一個跟你 extension property 同名的屬性,會用「原生」的那個,不會用你的。所以取名字要小心,特別是對 .NET 公開型別。

序列化/反射問題

extension property 不是「真正」的型別成員,只是語法糖。所以如果用到反射或序列化工具,extension property 可能會被忽略。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION