深入探討 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#