Linq下有一個非常實用的SelectMany方法,很多人卻不會用


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

一:SelectMany

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

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. 實現需求

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

        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才能搞定,再放張圖給大家看看,是不是看着頭大...😄

3. 優化辦法

如果你會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,很有意思😄

好了,本篇就說到這里,希望對你有幫助。


如您有更多問題與我互動,掃描下方進來吧~



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM