Linq下有一個很是實用的SelectMany方法,不少人卻不會用

在平時開發中常常會看到有些朋友或者同事在寫代碼時會充斥着各類for,foreach,這種程式代碼太多的話閱讀性特別差,並且還顯得特別累贅,其實在FCL中有不少幫助咱們提升閱讀感的方法,而現實中不少人不會用或者說不知道,這篇我就跟你們聊一聊。api

一:SelectMany

這個方法絕對是提升開發速度的一大利器,有太多的業務場景須要使用這個函數,舉一個我實際應用場景,商家按照年份和客戶類型預先設置一些標籤,而後讓系統跑一下它的各自標籤到底有多少人?ide

1. 定義Model函數

爲了方便演示,這裏作了一下簡化代碼,只有一個字典,key表示年份,value:就是該年份的多組標籤。性能

public class EstimateModel
{
    public int ShopID { get; set; }

    //key:年份
    public Dictionary<string, List<TagCrowdFilterModel>> YearCrowdFilterDict { get; set; }
}

public class TagCrowdFilterModel
{
    /// <summary>
    /// 篩選條件
    /// </summary>
    public string CrowdFiter { get; set; }

    /// <summary>
    /// 獲取人數
    /// </summary>
    public int TotalCustomerCount { get; set; }
}

爲了更加清晰,我決定再填充一下數據優化

public static void Main(string[] args)
    {
        var estimateModel = new EstimateModel()
        {
            ShopID = 1,
            YearCrowdFilterDict = new Dictionary<string, List<TagCrowdFilterModel>>()
            {
                {
                    "17年",new List<TagCrowdFilterModel>()
                           {
                             new TagCrowdFilterModel(){ CrowdFiter="between 10 and 20" },
                             new TagCrowdFilterModel(){ CrowdFiter=" a<10  || a>30" },
                           }
                },
                {
                    "18年",new List<TagCrowdFilterModel>()
                           {
                             new TagCrowdFilterModel(){ CrowdFiter="between 100 and 200" },
                             new TagCrowdFilterModel(){ CrowdFiter=" a<100  || a>300" },
                           }
                },
                {
                    "19年",new List<TagCrowdFilterModel>()
                           {
                             new TagCrowdFilterModel(){ CrowdFiter="between 1000 and 2000" },
                             new TagCrowdFilterModel(){ CrowdFiter=" a<1000  || a>3000" },
                           }
                }
            }
        };
    }

    public static int GetCustomerID(string crowdfilter)
    {
        return BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 0);
    }

2. 實現需求ui

需求也很簡單,就是依次獲取 TagCrowdFilterModel 中的 CrowdFiter 字段再調用GetCustomerID方法把人數賦值給TotalCustomerCount便可,這麼簡單的需求,若是讓你來搞定,你該怎麼實現這個邏輯?沒錯,不少人可能就是兩個foreach搞定。this

foreach (var year in estimateModel.YearCrowdFilterDict.Keys)
    {
        var yearCrowdFitlerList = estimateModel.YearCrowdFilterDict[year];

        foreach (var crowdFitler in yearCrowdFitlerList)
        {
            crowdFitler.TotalCustomerCount = GetCustomerID(crowdFitler.CrowdFiter);
        }
    }

看似代碼也很清爽,但現實哪有這麼好的事情,真實狀況是年份上可能還要套上一個客戶類型,客戶類型之上再套一個商品,商品之上再套一個商家,這樣很深的層級你就須要多達3個foreach,4個foreach甚至5個foreach才能搞定,再放張圖給你們看看,是否是看着頭大...
Linq下有一個很是實用的SelectMany方法,不少人卻不會用code

3. 優化辦法blog

若是你會selectMany,那隻須要一個鏈式寫法就能夠搞定,是否是簡單粗暴,雖然性能比不上命令式寫法,但可讀性和觀賞性真的上了幾個檔次。開發

estimateModel.YearCrowdFilterDict.SelectMany(m => m.Value).ToList().ForEach(m =>
            m.TotalCustomerCount = GetCustomerID(m.CrowdFiter)
        );

二:原理探究

1. msdn解釋

將序列的每一個元素投影到 IEnumerable,並將結果序列合併爲一個序列,並對其中每一個元素調用結果選擇器函數,連接:https://docs.microsoft.com/zh-cn/dotnet/api/system.linq.enumerable.selectmany?view=netframework-4.8 有了上面的案例解釋,再看msdn的這句話,我想你應該不折不扣的明白了selectMany怎麼使用。

2. 翻查源碼

宏觀上明白了,接下來用ILSpy去查下微觀代碼,到底這玩意是怎麼實現的。

public static IEnumerable<TResult> SelectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (selector == null)
    {
        throw Error.ArgumentNull("selector");
    }
    return SelectManyIterator(source, selector);
}

private static IEnumerable<TResult> SelectManyIterator<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
{
    foreach (TSource item in source)
    {
        foreach (TResult item2 in selector(item))
        {
            yield return item2;
        }
    }
}

你們仔細體會下這兩個foreach,尤爲是第二個foreach,其中的selector(item)不就是年份下的標籤集合嗎?再遍歷這個集合把每個item返回出去,返回值是IEnumerable,這得益於yield語法糖,它本質上就是一個編譯器封裝好的迭代類,作到了一個延遲,按需執行,後面我會專門分享一下yield,頗有意思

好了,本篇就說到這裏,但願對你有幫助。

相關文章
相關標籤/搜索