在 C# 3.0 引入擴展方法前,為現有類型(尤其是 sealed 類、第三方程序集類型、介面)添加行為極其困難:
sealed 類不可繼承;介面修改會強制所有實現類重新編譯,風險極大。StringHelper.TrimSpaces(str) 不如 str.TrimSpaces() 自然。擴展方法從根本上解決了:在不修改原類型定義、不創建子類的前提下,為其「注入」實例方法。
JS 中通過 prototype 動態為構造函數添加方法:
function Vector(x, y) { this.x = x; this.y = y; }
Vector.prototype.add = function(v) {
return new Vector(this.x + v.x, this.y + v.y);
};
var v = new Vector(1, 2);
v = v.add(v); // 如同實例方法調用
C# 作為靜態強類型語言,無法運行時動態修改類型元數據,擴展方法在編譯期以語法糖形式實現了同樣體驗。
public interface IVector {
double X { get; set; }
double Y { get; set; }
// 新增方法導致所有實現類必須修改
IVector Add(IVector v);
}
若 Vector 來自簽名程序集且為 sealed,傳統手段完全無法擴展。
static 且非泛型、非巢狀。static。this 修飾,類型即為擴展目標(this Vector v 表示擴展 Vector)。ref/out 參數。// 原類型(不可修改)
public class Vector {
public double X { get; set; }
public double Y { get; set; }
}
// 擴展方法定義
public static class VectorExtensions {
public static Vector Add(this Vector v1, Vector v2) {
return new Vector { X = v1.X + v2.X, Y = v1.Y + v2.Y };
}
}
// 使用(需 using 命名空間)
var v = new Vector { X = 1, Y = 2 };
v = v.Add(v); // 編譯器轉為 VectorExtensions.Add(v, v)
Console.WriteLine($"X={v.X}, Y={v.Y}"); // X=2, Y=4
using 所在命名空間時生效,避免全局污染。Vector 自身有 Add,調用實例版本;擴展方法作為後備。this IEnumerable<T> source 使所有集合獲得 LINQ 算子。public static class EnumerableExtensions {
public static TSource FirstOrDefault(
this IEnumerable source,
Func predicate)
{
foreach (var item in source)
if (predicate(item)) return item;
return default;
}
}
public static class HtmlBuilderExtensions {
public static HtmlBuilder AddDiv(this HtmlBuilder b, string cls) { ... }
public static HtmlBuilder AddSpan(this HtmlBuilder b, string text) { ... }
}
// 使用
var html = new HtmlBuilder()
.AddDiv("container")
.AddSpan("Hello")
.AddDiv("footer");
System)定義擴展方法,防止命名衝突。擴展方法調用 v.Add(v2) 在編譯期被重寫為靜態調用 VectorExtensions.Add(v, v2)。IL 中不存在任何動態分發機制,零運行時開銷。這正是 C# 「語法糖」設計哲學的典範:以極小語言變更賦能極大表達力提升。