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 成員
}
}
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;
}
}
}
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 可能會被忽略。
GO TO FULL VERSION