擴展方法過多會汙染 IntelliSense,導致原生方法被淹沒。本文探討三種分組管理策略,平衡「易發現性」與「介面整潔」。
MyExt.Chinese, MyExt.Regex)using MyExt.Chinese;public static class StringExtensions
{
public static ChineseString AsChinese(this string s) => new ChineseString(s);
public static ConvertibleString AsConvertible(this string s) => new ConvertibleString(s);
public static RegexString AsRegex(this string s) => new RegexString(s);
}
public class ChineseString
{
private readonly string _value;
public ChineseString(string value) => _value = value;
public string ToSBC() => /* 全形轉換 */ "";
public string ToDBC() => /* 半形轉換 */ "";
public string GetChineseSpell() => /* 拼音首字母 */ "";
}
// 使用: "abc".AsChinese().ToDBC()
AsXXX 群組,原生方法不受干擾。核心概念:單一進入點 As<T>() + 介面定義功能群組
public interface IExtension<T> { T GetValue(); }
public static class ExtensionGroup
{
private static readonly Dictionary<Type, Type> _cache = new();
public static T As<T>(this string value) where T : IExtension<string>
=> As<T, string>(value);
public static T As<T, V>(this V value) where T : IExtension<V>
{
var valueType = typeof(V);
if (!_cache.TryGetValue(valueType, out var implType))
{
implType = CreateImplementation<T, V>();
_cache[valueType] = implType;
}
return (T)Activator.CreateInstance(implType, value);
}
// 使用 Reflection.Emit 動態生成實作介面的類別
private static Type CreateImplementation<T, V>() where T : IExtension<V>
{
var iface = typeof(T);
var name = iface.Name.Substring(1); // 移除 'I' 前綴
var asmName = new AssemblyName("DynamicExtensions");
var asmBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
var modBuilder = asmBuilder.DefineDynamicModule(asmName.Name);
var typeBuilder = modBuilder.DefineType(name, TypeAttributes.Public);
typeBuilder.AddInterfaceImplementation(iface);
// 私有欄位 _value
var field = typeBuilder.DefineField("_value", typeof(V), FieldAttributes.Private);
// 建構子
var ctor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(V) });
var il = ctor.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Stfld, field);
il.Emit(OpCodes.Ret);
// GetValue 方法
var method = typeBuilder.DefineMethod("GetValue", MethodAttributes.Public | MethodAttributes.Virtual, typeof(V), Type.EmptyTypes);
il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, field);
il.Emit(OpCodes.Ret);
// 介面映射
typeBuilder.DefineMethodOverride(method, iface.GetMethod("GetValue"));
return typeBuilder.CreateType();
}
}
// 1. 定義介面 (繼承 IExtension<string>)
public interface IChineseString : IExtension<string> { }
public interface IConvertibleString : IExtension<string> { }
public interface IRegexString : IExtension<string> { }
// 2. 對介面進行擴展 (而非對類別)
public static class ChineseStringExtensions
{
public static string ToSBC(this IChineseString s) => /* 實作 */ "";
public static string ToDBC(this IChineseString s) => /* 實作 */ "";
public static string GetSpell(this IChineseString s) => /* 實作 */ "";
}
public static class ConvertibleStringExtensions
{
public static bool IsInt(this IConvertibleString s) => int.TryParse(s.GetValue(), out _);
public static int ToInt(this IConvertibleString s) => int.Parse(s.GetValue());
public static DateTime ToDateTime(this IConvertibleString s) => DateTime.Parse(s.GetValue());
}
// 僅一個 As<T>() 出現在字串的 IntelliSense 中
var result = "123".As<IConvertibleString>().ToInt();
var spell = "測試".As<IChineseString>().GetSpell();
var match = "abc123".As<IRegexString>().Match(@"\d+");
| 維度 | 命名空間 | 前綴分組 | 介面導向 |
|---|---|---|---|
| IntelliSense 汙染 | 中 | 低 | 極低 (僅 1 個 As<T>) |
| 呼叫便利性 | 高 | 中 (多一層) | 中 (需指定介面型別) |
| 擴充性 | 高 | 高 | 高 (新增介面+擴展類別) | 實作複雜度 | 低 | 低 | 高 (需 Reflection.Emit) |
| 擴展方法圖示 | 正常 | 混雜 | 正常 (對介面擴展) |
介面導向分組雖實作較複雜,但提供最佳的開發體驗:單一進入點、功能語意分組、完美的 IntelliSense 整潔度。建議核心類庫採用此架構,應用層專案可採用較簡單的前綴分組即可。