Refactoring to LINQ => ForEach

栏目: ASP.NET · 发布时间: 5年前

内容简介:LINQ 是 C# 3 實現 FP 重要里程碑,提供大量的 Operator,讓我們以 Pure Function 將 data 以 dataflow 方式實現。本系列將先以 Imperative 實作,然後再重構成 FP,最後再重構成 LINQ Operator,並參考 LINQ source code 的實現方式。首先從最基本的macOS High Sierra 10.13.6

LINQ 是 C# 3 實現 FP 重要里程碑,提供大量的 Operator,讓我們以 Pure Function 將 data 以 dataflow 方式實現。本系列將先以 Imperative 實作,然後再重構成 FP,最後再重構成 LINQ Operator,並參考 LINQ source code 的實現方式。

首先從最基本的 ForEach Operator 談起。

Version

macOS High Sierra 10.13.6

.NET Core 2.1

C# 7.2

Rider 2018.1.4

User Story

在 List 中有一堆名字,想要在 console 顯示每個名字。

using System.Collections.Generic;

namespace ConsoleApp
{
    class Program
    {
        static void Main()
        {
            var names = new List<string>
            {
                "Ben",
                "Jafar",
                "Matt",
                "Priya",
                "Brian"
            };
        }
    }
}

Imperative : for

using System;
using System.Collections.Generic;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var names = new List<string>
            {
                "Ben",
                "Jafar",
                "Matt",
                "Priya",
                "Brian"
            };

            for (var counter = 0; counter < names.Count; counter++)
            {
                Console.WriteLine(names[counter]);
            }
        }
    }
}

19 行

for (var counter = 0; counter < names.Count; counter++)
{
    Console.WriteLine(names[counter]);
}

最直覺的方式 (其實應該說被制約的方式), 就是透過 List 的 Indexer 將 List 的 item 取出來,並透過 for loop 去執行 Console.WriteLine()

Imperative : foreach

Refactoring to LINQ => ForEach

此時 Rider 已經提出警告,建議改用 foreach

Refactoring to LINQ => ForEach

  1. 按熱鍵 ⌥ + ↩ ,選擇 Convert to foreach
using System;
using System.Collections.Generic;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var names = new List<string>
            {
                "Ben",
                "Jafar",
                "Matt",
                "Priya",
                "Brian"
            };

            foreach (var name in names)
            {
                Console.WriteLine(name);
            }
        }
    }
}

19 行

foreach (var name in names)
{
    Console.WriteLine(name);
}

重構成 foreach 後,就不必使用 counter 變數,也不用考慮 counter++ 寫錯, for loop 的 counter 控制,是常見的 bug 來源,所以重構成 foreach 絕對比 for 來得好。

foreach 的語意也比 for 清楚 ,因為不用看到 counter 實作細節。

FP : Higher Order Function

實務上這種 foreach 天天都要用到,但用 foreach 這種 statement 寫法,重複使用能力為 0,就每天都要不斷的寫 foreach

若我們能將 foreach 抽成 ForEach() Higher Order Function,我們就能不斷 reuse ForEach() ,只要將不同的商業邏輯以 function 傳進 ForEach() 即可。

using System;
using System.Collections.Generic;

namespace ConsoleApp
{
    static class Program
    {
        static void Main(string[] args)
        {
            var names = new List<string>
            {
                "Ben",
                "Jafar",
                "Matt",
                "Priya",
                "Brian"
            };

            names.MyForEach(name => Console.WriteLine(name));
        }
        
        private static void MyForEach<T>(this List<T> list, Action<T> action)
        {
            foreach (var iter in list)
            {
                action(iter);
            }
        }
    }
}

22 行

private static void MyForEach<T>(this List<T> list, Action<T> action)
{
    foreach (var iter in list)
    {
        action(iter);
    }
}

自己以 MyForEach() 實作出 foreach 的 Higher Order Function 版本。

第 9 行

names.MyForEach(name => Console.WriteLine(name));

原來的 foreach statement 重構成 MyForEach() Higher Order Function 版本,只要將 Console.WriteLine() 改用 Lambda 傳入 MyForEach() 即可,如此 foreach 就能被 reuse 了。

不過 C# 在此可能感受不到 FP 所謂的 Dataflow 與 Pipeline,就語意而言,只能說我們為 List 寫了一個新 method : MyForEach() ,也就是 C# 所謂的 Extension Method,這符合傳統 C# 的 OOP 思維。

他山之石可以攻錯,我們來看看 F# 語法。

let names = ["Ben"; "Jafar"; "Matt"; "Priya"; "Brian"]

let MyForEach action list = 
    for iter in list do
        action iter
names 
|> MyForEach (printfn "%A ")

names 為 List, myForEach() 為自己實作的 ForEach() Higher Order function,若不懂 F# 語法先略過細節沒關係。

|> 為 pipline,所以可以明顯看出其語意為 names data 以 Dataflow 與 Pipeline 方式傳給 myForEach() 執行 printf()

但 C# 並沒有特別提供 |> 這種 pipeline 符號,而是繼續使用 OOP 的 .

. 雖然方便,但心裡要知道,這裡的 . 並不是 OOP 的 method,而是 FP 的 Dataflow 與 Pipeline,只是也使用了 . 為符號,其本質是 F# 的 |>. 算是 |> 的 syntax sugar。

private static void MyForEach<T>(this List<T> list, Action<T> action)
{
    foreach (var iter in list)
    {
        action(iter);
    }
}

再舉例另外一個例子證明 . 並非 OOP 的 method,而是 FP 的 Pipeline。

我們實現 MyForEach() 時,並不是寫在 List class 內,而是透過 static method + this 參數成為 Extenstion Method,也就是 List data 與 MyForEach() logic 是徹底分離,而非如 OOP 將 data 與 logic 寫在 List class 內,也再次證明 Extension Method 是 C# 實踐 Pipeline 的手法,而 . 只是 syntax sugar,其本質是 FP 的 Pipeline。

IEnumrable

Refactoring to LINQ => ForEach

當初我們只是想為了 List 重構,所以 Extension Method 的型別只用了 List,Rider 提出建議: List<T> 其實可以重構成 IEnumerable<T> ,如此 MyForEach() 重複使用能力更高。

Refactoring to LINQ => ForEach

  1. 將 cursor 放在 List 上,按熱鍵 ⌥ + ↩ ,選擇 Make parameter type IEnumerable<T>

Refactoring to LINQ => ForEach

  1. List<T> 重構成 IEnumerable<T>

Method Group

Refactoring to LINQ => ForEach

Rider 建議將 Lambda 重構成 Method Group。

MyForEach() 後接 Lambda 天經地義,但若 function 的型別定義的很清楚,讓 compiler 可以找到正確 signature 的 Overloading method,則不用寫 Lambda,傳入 method 名稱即可,這就是 Method Group,會讓 FP 寫法更加精簡。

MyForEach() 很明確定義了 Action<T> ,就只有一個 input 參數的 function,因此 compiler 有足夠的資訊找到 Console.WriteLine() 正確的 Overloading 版本,因此適合重構成 Method Group。

Refactoring to LINQ => ForEach

  1. 將 cursor 放在 WriteLine 上,按熱鍵 ⌥ + ↩ ,選擇 Replace with method group

Refactoring to LINQ => ForEach

  1. 只要傳入 Method 名稱即可,不用寫 Lambda

Using static

其實目前的 Console.WriteLine 已經非常精簡,可讀性也高,但 C# 6 提供了 using static ,可以讓你寫出更 FP 風格的 code。

Refactoring to LINQ => ForEach

  1. 將 cursor 放在 Console 上,按熱鍵 ⌥ + ↩ ,選擇 Import static members

Refactoring to LINQ => ForEach

  1. System.Console 使用 using static
  2. MyForEach() 只要傳入 WriteLine 即可,更像是 function

LINQ : ForEach

其實 ForEach() 這種很普遍的東西,在 LINQ 早已內建,我們改用 LINQ 版本。

Refactoring to LINQ => ForEach

  1. 直接使用 LINQ 的 ForEach() ,將自己寫的 MyForEach() 刪除

LINQ 如何實踐 ?

Refactoring to LINQ => ForEach

我們來看看 LINQ 的 source code 如何實踐 ForEach() ?

一開始對 action 進行判斷,若沒有則拋出 exception。

action 也搭配 for 進行,跟我們使用 foreach 類似。

基本上 LINQ 的 ForEach() 跟我們自己寫的 MyForEach() 差不多,但更加嚴謹

使用時機

ForEach() 表面上看起來是 foreach statement 的 syntax sugar,但事實上 ForEach() 在 FP 的意義並非如此。

FP 將 data 以 Dataflow 與 Pipeline 方式處理,因此提供了眾多 operator,而 operator 則必須搭配 pure function,不能有 Side Effect。但 Side effect 總要有人處理, ForEach() 就是讓你統一處理 Side Effect 之處。

與 Imperative 寫法的差異是 : Imperative 總是不斷的在處理 Side Effect,因此造成結果難以預測、難以測試,bug 就是由此展開;但 FP 對於 data 處理堅持採用 Dataflow 與 Pipeline,不使用 Side Effect,因此對 data 處理是可預測且容易測試,直到最後 data 處理完,才不得已使用 ForEach() 處理 Side Effect。

Console.WriteLine() 就是 I/O,就是 Side Effect,這是無法避免的,最後使用 ForEach() 統一解決。

Conclusion

  • . 與 Extension Method 是 C# 的 syntax sugar,其本質就是 FP 的 data 與 logic 分離與 Pipeline
  • Method Group 讓 FP 寫法會更為精簡,也是 C# 很重要的發明
  • FP 的 operator 主要在處理 Dataflow 與 Pipeline,應該使用 pure function,但 ForEach() 是少數讓你處理 Side Effect 的 operator,應該將 side effect 集中在 ForEach() 處理

Reference

ReactiveX , Functional Programming in JavaScript


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

一网打尽

一网打尽

[美]布拉德·斯通 / 李晶、李静 / 中信出版社 / 2014-1-15 / 49.00元

亚马逊最早起步于通过邮购来经营图书业务。但贝佐斯却不满足于仅做一名书商,他希望缔造亚马逊万货商店的神话——能提供海量的货源,并以超低的价格提供最具吸引力的便捷服务。为了实现这一诺言,他发展了一种企业文化,这种文化蕴含着执着的雄心与难以破解 的秘诀。亚马逊的这 一文化现在依旧在发扬光大。 布拉德·斯通非常幸运地得到采访亚马逊的前任和现任高管、员工以及贝佐斯本人、家人的机会,使我们第一次有机会深......一起来看看 《一网打尽》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具