深入探討 C# 之 Extension Method
C# 3 的 Extension Method 是很了不起的發明,讓我們在不修改原本 source code 的前提下,就能為 class 增加新 method,實現開放封閉原則,尤其對於 .NET Framework 或 package 的擴展特別有效。
事實上 Extension Method 在 Functional Programming 下另有妙用,讓我們輕易實現 Function Composition。
Version
macOS High Sierra 10.13.6
.NET Core 2.1
C# 7.2
Rider 2018.1.3
Extension Method
首先來看 Extension Method 最標準的應用。
Program.cs
1 | using System; |
先使用 Enumerable.Range() 產生 1, 2, 3,再使用 Select() 變成 2, 4, 6,最後使用 ForEach() 印出來。
但中間出現了一個很詭異的 ToList()。
原因是因為 IEnumerable 沒有 ForEach(),只有 List 才有 ForEach(),因此我們必須先 ToList()。
但就語意來說,這是很怪的,IEnumerable 自帶 Aggregate(),為什麼卻沒有提供更常用的 ForEach() 呢 ?
既然 .NET Framework 沒有,我們就來替 IEnumerable 打造一個 ForEach() 吧。
Extensions.cs
1 | using System; |
ForEach() 第一個參數為 this IEnumerable<T>,注意特別加了 this,表示我們要為 IEnumerable 提供 ForEach() Extension Method。
且 Extension Method 必須都為 static method。
Program.cs
1 | using System; |
之後我們就能將奇怪的 ToList() 拿掉了,這樣的語意是不是更好呢 ?
Extension Method 讓我們對 .NET Framework 或 package 加以擴充,用起來好像是內建的method 一樣,只要做兩件事情 :
- 使用 static method
- 第一個參數為該 class 或 interface 型別,並加上
this修飾
Function Composition
Function Composition 是 FP 的招牌菜,強調藉由眾多的小 functional 組合成大 function,而非傳統 imperative 寫法,總是寫出數百行的 method,且這些 method 幾乎是量身定做,不只重複使用的機會很低,且因為行數過多很難維護,也難以單元測試。
Program.cs
1 | using System; |
EmailFor() 能根據 user 的 姓 與 名 自動產生 email。
11 行
1 | string EmailFor(Person person) => AppendDomain(AbbreviateName(person)); |
EmailFor() 為 C# 7 的 Local Function,藉由 AbbreviateName() 與 AppendDomain() 組合出新的 EmailFor(),這也就是所謂的 Function Composition : h = fog = f(g),其中 g 就是 AbbreviateName(),而 f 就是 AppendDomain()。
這種數學式的 Function Composition 雖然重複使用性極高,但並不容易閱讀,程式碼必須 由右而左,違反一般人 由左至右 的閱讀習慣,因此想改用 Function Pipeline 方式變成 由左至右。
Email.cs
1 | namespace ConsoleApp |
將 AbbreviateName() 與 AppendDomain() 的第一個參數都改加上 this,搖身一變成為 Extension Method。
Program.cs
1 | using System; |
則原本的 EmailFor() Local Function 就不需要了,只要將 Person new 後,直接如 LINQ 般去 AbbreviateName() 與 AppendDomain(),這種風格維持了 由左至右 的閱讀習慣,非常清楚。
只要將第一個參數加上
this修飾成為 Extenstion Method 後,就可由 Function Composition 改成 Function Pipeline 風格
Refactoring
若 Legacy code 只用了 Function Composition,可以使用 Rider 幫我們重構成 Function Pipeline。

Legacy code 並沒有使用 Extension Method。

- 將 cursor 放在 method 上
- 按熱鍵
⌃ + T,選擇Convert Static Method to Extension

- Rider 自動幫我們將第一個參數加上
this

- 使用端會重構成 Function Pipeline

EmailFor 與 john 可以進一步 Inline 掉。
Conclusion
- Function Composition 與 Function Pipeline 講的是同一件事情,只是 Function Composition 偏數學,採用
由右至左,而 Function Pipeline 偏閱讀習慣,採用由左至右 - Funciton Compostion 與 Function Pipeline 是 FP 極關鍵部分,以前總以為 C# 沒有支援,因此無法使用 C# 寫 FP,有了 Extension Method,C# 就能很輕鬆的實踐 FP
- Rider 支援
Convert static method to Extension,讓我們快速重構成 Extension Method,再加上Inline Method與Inline Variable,最後就會重構出 Function Pipeline
Sample Code
完整的範例可以在我的 GitHub 與 GitHub 上找到
Reference
Enrico Buonanno, Functional Programming in C#