Refactor to LINQ => Select

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

内容简介:LINQ 是 C# 3.0 實現 FP 重要里程碑,提供大量的 Operator,讓我們以 Pure Function 將 data 以 Dataflow 與 Pipeline 方式實現。本系列將先以 Imperative 實作,然後再重構成 FP,最後再重構成 LINQ Operator。本文將討論macOS High Sierra 10.13.6

LINQ 是 C# 3.0 實現 FP 重要里程碑,提供大量的 Operator,讓我們以 Pure Function 將 data 以 Dataflow 與 Pipeline 方式實現。本系列將先以 Imperative 實作,然後再重構成 FP,最後再重構成 LINQ Operator。

本文將討論 Select Operator。

Version

macOS High Sierra 10.13.6

.NET Core 2.1

C# 7.2

Rider 2018.2.3

Imperative

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var data = Enumerable.Range(1, 3);

            var result = new List<int>();

            foreach (var item in data)
            {
                result.Add(item * 2);
            }

            foreach (var item in result)
            {
                Console.WriteLine(item);
            }
        }
    }
}

使用 Enumerable.Range() 產生 1, 2, 3 ,建立暫存的 result List,將所有元素都乘以 2 ,最後使用 foreach() 印出每個值。

由於資料的改變,建立新的暫存 List 處理,是 Imperative 慣用手法。

Refactor to HOF

實務上這種建立新暫存 List 處理的作法,常常會遇到,若每次都使用 foreach 這種 statement 寫法,重複使用能力為 0,就每次都要不斷的寫 foreach

若我們能將這種 foreach 配合新暫存 List 處理做法,抽成 MyMap() Higher Order Function,我們就能不斷 reuse MyMap() ,只要將不同的商業邏輯以 function 傳進 MyMap() 即可。

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var data = Enumerable.Range(1, 3);

            var result = MyMap(data, Double);

            foreach (var item in result)
            {
                Console.WriteLine(item);
            }
            
            int Double(int x) => x * 2;
        }
        
        private static IEnumerable<int> MyMap(IEnumerable<int> data, Func<int, int> func)
        {
            var result = new List<int>();

            foreach (var item in data)
            {
                result.Add(func(item));
            }

            return result;
        }
    }
}

23 行

private static IEnumerable<int> MyMap(IEnumerable<int> data, Func<int, int> func)
{
    var result = new List<int>();

    foreach (var item in data)
    {
        result.Add(func(item));
    }

    return result;
}

自己以 MyMap() 實作出 foreach statement + 暫存 List 處理的 Higher Order Function 版本。

第一個參數為 data,第二個參數為 function。

如此 MyMap() function 就能被重複使用。

13 行

var result = MyMap(data, Double);

原來的 foreach() statement 重構成 MyMap() Higher Order Function,將 data 與 Double Local Function 傳入即可。

Refactor to Yield Return

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var data = Enumerable.Range(1, 3);

            var result = MyMap(data, Double);

            foreach (var item in result)
            {
                Console.WriteLine(item);
            }
            
            int Double(int x) => x * 2;
        }
        
        private static IEnumerable<int> MyMap(IEnumerable<int> data, Func<int, int> func)
        {
            foreach (var item in data)
            {
                yield return func(item);
            }
        }
    }
}

23 行

private static IEnumerable<int> MyMap(IEnumerable<int> data, Func<int, int> func)
{
    foreach (var item in data)
    {
        yield return func(item);
    }
}

但要建立暫存 List 會影響執行效率,也浪費記憶體,尤其暫存 List 只是中繼資料,並不是最後執行結果,因此改用 yield return 實現 Lazy Evaluation,直到真正需要結果時,才會執行 func(item) ,如此就不用建立暫存 List,繼能解省記憶體,又能增進執行效率。

Refactor to Generics

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var data = Enumerable.Range(1, 3);

            var result = MyMap(data, Double);

            foreach (var item in result)
            {
                Console.WriteLine(item);
            }
            
            int Double(int x) => x * 2;
        }
        
        private static IEnumerable<R> MyMap<T, R>(IEnumerable<T> data, Func<T, R> func)
        {
            foreach (var item in data)
            {
                yield return func(item);
            }
        }
    }
}

23 行

private static IEnumerable<R> MyMap<T, R>(IEnumerable<T> data, Func<T, R> func)
{
    foreach (var item in data)
    {
        yield return func(item);
    }
}

事實上 MyMap() 不只適用於 int ,而且可適用於任何型別,因此重構成 <T, R>

Refactor to Extension Method

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp
{
    static class Program
    {
        static void Main(string[] args)
        {
            Enumerable
                .Range(1, 3)
                .MyMap(Double)
                .ToList()
                .ForEach(Console.WriteLine);
                
            int Double(int x) => x * 2;
        }
        
        private static IEnumerable<R> MyMap<T, R>(this IEnumerable<T> data, Func<T, R> func)
        {
            foreach (var item in data)
            {
                yield return func(item);
            }
        }
    }
}

20 行

private static IEnumerable<R> MyMap<T, R>(this IEnumerable<T> data, Func<T, R> func)
{
    foreach (var item in data)
    {
        yield return func(item);
    }
}

MyMap() 需要兩個參數,使用上不是那麼方便,而且也無法 Pipeline 般使用,因此將第一個參數加上 this ,成為 Extension Method,

11 行

Enumerable
    .Range(1, 3)
    .MyMap(Double)
    .ToList()
    .ForEach(Console.WriteLine);

如此 MyMap() 就與 Range() 串起來了,而且也減少了一個參數。

Refactor to LINQ

using System;
using System.Linq;

namespace ConsoleApp
{
    static class Program
    {
        static void Main(string[] args)
        {
            Enumerable
                .Range(1, 3)
                .Select(Double)
                .ToList()
                .ForEach(Console.WriteLine);
                
            int Double(int x) => x * 2;
        }
    }
}

事實上 LINQ 早已提供 Select() ,不必我們自己實作,其功能完全等效於自己實作的 MyMap()

一般 FP 世界,將這種 operator 稱為 Map,如 ECMAScript 有 Array.prototype.map() ,F# 有 List.map() ,在 LINQ 則稱為 Select()

Refactor to Using Static

using static System.Linq.Enumerable;
using static System.Console;

namespace ConsoleApp
{
    static class Program
    {
        static void Main(string[] args)
        {
            Range(1, 3)
                .Select(Double)
                .ToList()
                .ForEach(WriteLine);

            int Double(int x) => x * 2;
        }
    }
}

使用 using static 之後,則 Range()WriteLine() 可進一步縮短,更符合 FP 風格。

Conclusion

  • 就算自己重構,也會重構出 Select() Higher Order Function,只是因為太常使用,LINQ 已經內建 Select()
  • Yield return 可實現 Lazy Evaluation,繼可節省記憶體,又可增進執行效率
  • 善用 using static ,可讓 class 的 static method 更像 function

Sample Code

完整的範例可以在我的 GitHub 上找到


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

零基础学算法

零基础学算法

戴艳 / 机械工业出版社 / 2009-1 / 59.80元

零基础学算法,ISBN:9787111284048,作者:戴艳 等编著一起来看看 《零基础学算法》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

URL 编码/解码
URL 编码/解码

URL 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具