1. 入门
以前只能给类加 Extension-methods。但想加属性(property)作为扩展是不行的!比如你想让 DateTime 有个 IsWeekend 属性,只能用方法实现,这样其实不太方便——尤其你想要 date.IsWeekend 这种语法,而不是 date.IsWeekend()。
随着 C# 14 的发布,梦想成真:现在可以用类似方法的方式写扩展属性了。你可以给已有类型加新属性,不用改源码,用起来就像普通属性一样!
哪里真的有用?
- 对于 .NET 标准类型和第三方库,源码你改不了。
- 在 view-model、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)
{
// 静态虚拟成员
}
}
新的 extension members 语法(C# 14+)
注意! 这个新的
extension 语法只在
C# 14+ 和
.NET 10+ 下能用。如果你用的是 .NET 9,那就
用不了。但如果你想试试,而
.NET 10 还没正式发布,可以装
.NET 10 preview 5+
提示! 我们的 C# 课程用的都是
C# 14+ 的最新特性,甚至有些还没正式发布。2025 年秋天
.NET 10 就出来了,等你学完课程,绝对是 C# 最前沿的玩家!😎
3. 扩展属性的语法
长啥样?
假如你想给 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 // 扩展属性!
{
get => source.DayOfWeek == DayOfWeek.Saturday || source.DayOfWeek == DayOfWeek.Sunday;
}
}
}
现在你可以这样写:
DateTime today = DateTime.Now;
if (today.IsWeekend)
{
Console.WriteLine("可以多睡一会儿!");
}
4. extension properties 有哪些类型
不只是只读属性,还能加读写属性:
public static class DateTimeExtensions
{
extension(DateTime source)
{
public int YearFrom1900 // 扩展属性!
{
get => source.Year - 1900;
set => source.Year = value + 1900;
}
}
}
可读写的扩展属性
注意: 对于可读写的 extension properties,你要明白它们会改原对象,但不能像自动字段那样单独存新值。如果你想加一个对象本身没有的属性,需要额外的存储(比如字典),这就超出基础用法了。
5. 静态属性
新语法让你不仅能给对象加虚拟属性,还能给类加!没错,现在可以给类加虚拟静态属性。语法几乎一样:
public static class DateTimeExtensions
{
// 新语法:DateTime 的 extension block(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 block:
public static class DogExtensions
{
extension(Dog)
{
public bool AllVaccinatedThisYear
{
get => Vaccinations.Any(v => v.Year == DateTime.Now.Year);
}
}
}
// 在程序里:
var myDog = new Dog { Name = "Buddy" };
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 属性帮不上忙
- 它们不能完全替代真实字段:没法访问对象的私有成员。
- 不能加 自动 状态变更(比如不能直接跟踪原类值的变化)。
- 不能用 extension property 实现接口或抽象类。
命名冲突
如果原类后来加了和你扩展属性同名的属性,优先用“原生”属性,不会用你的扩展。所以起名要小心,特别是用 .NET 的公共类型时。
序列化/反射问题
Extension property 其实不是“真实”成员,只是语法糖。所以用反射或序列化工具时,扩展属性可能不可见。
GO TO FULL VERSION