擴充方法的使用時機(個人觀點)
我個人在使用擴充方法時會考量的幾個時機點 :
- 取代 overload
- 功能單一原則
- Method Naming 符合其功能
- 自由擴充進出口
- 實現 Fluent Api
- 重新串接個物件的行為
- 易於實現多種 pattern
- 提升可讀性
- 提升程式性能
- 介面導向開發的輔助
撰寫擴充發法的原則
- 盡可能以單一lambda 或五行內(分號算一行) 完成
- 利用Method Name 明確表達其功能性並銜接前後類別名稱
- 不包含自己開發的商業邏輯
- 所屬的靜態類別以擴充對象命名 並加上 Extensions or Helper
- namespace name 會設定與目標取用namespace 相同
- 通常只串接interface 而非實體物件本身
案例: o : 我會這樣做 x :我可能不會這樣做
interface IRepository<T> {
void Add(T t); // o
void AddRange(...T[] ts); // x
void Add(CastToT cbt) // x
string Add(T t); // x
}
public static class RepositoryExtensions {
public static IRepository<T> Add(
this IRepository<T> repo,
params T[] ts) // 同型同名Method 會以本體為優先
{
if(t == null) throw new ArgumentException(ErrMsg); // Quick Fail
return ts.Aggregate(repo,(t,rp) => rp.Add(t)); // 累加的方式可以視情況而定 並同時實現Fluent
}
public static IRepository<T> AddRange( // 明確定義這個只能用在Array Like Argument
this IRepository<T> repo,
IEnumerable<T> ts, // 額外定義IEnumerable 可減少param 而必須ToArray 的情境
string ErrMsg = default(string)) // 可由使用者自行定義錯誤訊息 Func<T,string> format = default(Func<,>) 亦可
=> ts.IsNotNull(ErrMsg)
.Aggregate(
repo,
(t,rp) => rp.Add(t)
); // 實現 one line
public static IRepository<T> AddToRepo(
this IEnumerable<T> ts,
IRepository<T> repo
)
=> ts.Aggregate(repo,(t,rp) => rp.Add(t)); // 反向串接 有必要的話
// ts.Select(t => repo.Add(t)); // if return IEnumerable<T>
}
public static class NullCheckHelper {
public static T IsNotNull(this T t,string ErrMsg) // 額外定義輔助擴充方法
=> t != null ? t : throw new ArgumentException(ErrMsg,t); // inline throw allow in C# 7
}
public class foo {
public void foo(IRepository<T> t) {
t.Add(new T()).Add(Enumerable.Range(0,10).Select(i => new T()));
Enumerable.Range(0,10).Select(i => new T()).AddToRepo(t);
}
}
案例:Pattern 實作
public static class PatternExtensions {
// Use in branching without if else or switch
public static TResult WorkEntry<T,TResult>(this T t)
=> new WorkWithA(t).Result ??
public static TResult WorkWithA<T,TResult>(this T t)
=> new WorkWithA(t).Result ?? this.WorkWithB(t);
public static TResult WorkWithB<T,TResult>(this T t)
=> new WorkWithB(t).Result ?? this.WorkWithC(t);
public static TResult WorkWithC<T,TResult>(this T t)
=> new WorkWithC(t).Result ?? throw new Exception("it's not work");
public static UseWorkEntry() => new T().WorkEntry();
// Logger Decorator
public static FuncT,T> WorkEntryWithLog<T>(this T t,Func<T,T> logger)
=> t => logger(next(t)));
public static T DecorateWorkWithA(this Func<T,T> lastWork,Func<T,T> newWork,T realt)
=> (t => lastWork(newWork(t)))(realt);
public static T UseWorkWithLogEntry()
=> new T().WorkEntryWithLog(
x => Console.Write(x),
x => new WorkWithA.Result(x) ?? x.WorkWithB();
}).;
}