Refactor to LINQ => Select

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

内容简介: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 上找到


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

查看所有标签

猜你喜欢:

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

Google

Google

托马斯·舒尔茨(Thomas·Schulz) / 严孟然、陈琴 / 当代中国出版社 / 2016-11-1 / CNY 49.80

想要掌握未来,必须了解谷歌 1998年从车库起家,短短数年研发上千项专利,2016年力压苹果、亚马逊,成为“世界最具价值品牌”“最佳雇主”,谷歌无疑是互联网时代的最大赢家,这家公司有能力通过巨额广告利润收获现在,更有意愿在人工智能层面创造未来。 据说谷歌势不可挡,永不餍足。从互联网搜索到智能翻译再到地图导航,谷歌是我们通向世界的门户。不仅如此,就像管理全世界的数据流一样,谷歌还要在不久......一起来看看 《Google》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

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

HTML 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具