C# Linq源码解析之Aggregate

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C# Linq源码解析之Aggregate相关的知识,希望对你有一定的参考价值。

前言

在Dotnet开发过程中,Aggregate作为IEnumerable的扩展方法,十分常用。本文对Aggregate方法的关键源码进行简要分析,以方便大家日后更好的使用该方法。

使用

Aggregate是对序列应用累加器的函数。

看下面一段代码:

List<string> lst = new List<string>()  "张三", "李四", "王麻子" ;

给了我们这样的一个list集合,我们想要得到

"张三 哈哈哈 李四 哈哈哈 王麻子 "

这样的一个结果;

假如我们没有linq,我们该怎么去写这个代码呢?

可能和下面这段代码没有多少出入!

string str = "";

for (int i = 0; i < lst.Count; i++)

  str += lst[i] + " 哈哈哈 ";

Console.WriteLine(str);

那如果使用linq的扩展方法Aggregate就显得简单的多了

str = lst.Aggregate((first, second) => $"first 哈哈哈 second ");

那我肯定不只是给大家介绍这个东西怎么写的,我们是来刨析它的原理的。我们来看看他的原型

public static TSource Aggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func)

Func<TSource, TSource, TSource> func 可以看出方法接收一个委托,该委托接收两个TSource类型的参数,第一个参数是记录值,第二个参数是从IEnumerable中取下一个元素,最后一个是返回累加器的最终值。

可能有人对前面的lambda怎么演变的不清楚

(first, second) => $"first 哈哈哈 second "

我这里再讲一下,具体的内容请看我的文章你真的了解Lambda表达式吗?

其实这个lambda表达式转换成方法就是这样的

public string MyAdd(string first, string second)
        
            return $"first 哈哈哈 second " ;
        

然后这样去使用,输出的结果也是一样的

str = lst.Aggregate(MyAdd);
 Console.WriteLine(str);

看到这里你可能还是很疑惑,那Aggregate内部的运行原理是怎样的呢?我们定义一个扩展方法跟Aggregate的原型一样我们来一步一步的实现它。我们声明一个MyAggregate

public static TSource MyAggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func)

我们来一步一步的实现它的源码

我们方法内部肯定是要对参数进行判空校验的

public static TSource MyAggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func)

  if (source == null)
   
    throw new Exception("source is null");
   

   if (func == null)
    
    throw new Exception("func is null");
   

扩展方法是静态类的静态方法,其实第一个参数用this修饰符代表源元素的类型,且是一个可进行迭代的源元素类型

this IEnumerable<TSource> source

那么我们就知道这个源元素是一个 可以获得循环访问集合的枚举器那么我们就可以使用GetEnumerator这个方法进行迭代了,虽然我们对源元素进行了判空,我们程序为了严谨性,我们肯定要对源元素里面是否有内容进行判空

using (IEnumerator<TSource> enumerator = source.GetEnumerator())
            
                if (!enumerator.MoveNext())
                
                    throw new Exception("enumerator is null");
                
      

然后我们声明一个返回的类型接收当前值,然后使用while循环遍历源元素的值,最后返回值,这样我们的Aggregate源码就实现了

public static TSource MyAggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func)
        
            if (source == null)
            
                throw new Exception("source is null");
            

            if (func == null)
            
                throw new Exception("func is null");
            
           
            using (IEnumerator<TSource> enumerator = source.GetEnumerator())
            
                if (!enumerator.MoveNext())
                
                    throw new Exception("enumerator is null");
                

                TSource val = enumerator.Current;
                while (enumerator.MoveNext())
                
                    val = func(val, enumerator.Current);
                

                return val;
            
        

我们的Aggregate是有三个重载方法的,我们实现了最简单的一个

那我们来看第二个

public static TAccumulate Aggregate<TSource, TAccumulate>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func

就比第一个方法多了Accumulate seed 将指定的种子值用作累加器初始值。

然后我们能使用这个方法

str = lst.Aggregate("我的输出结果", (first, second) => $"first 哈哈哈 second ");
            Console.WriteLine(str);

那怎么去理解这个将指定的种子值用作累加器初始值,下面的这段代码和上面等同

str = "我的输出结果"+lst.Aggregate( (first, second) => $"first 哈哈哈 second ");
            Console.WriteLine(str);

我们就可以实现该源码了,由于我们初始值有了,所以不用对元素里面是否有内容进行判空,直接使用foreach遍历

public static TAccumulate Aggregate<TSource, TAccumulate>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func)
        
            if (source == null)
            
                throw Error.ArgumentNull("source");
            

            if (func == null)
            
                throw Error.ArgumentNull("func");
            

            TAccumulate val = seed;
            foreach (TSource item in source)
            
                val = func(val, item);
            

            return val;

那我们再来看第三个

public static TResult Aggregate<TSource, TAccumulate, TResult>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func, Func<TAccumulate, TResult> resultSelector)

对序列应用累加器函数。将指定的种子值用作累加器的初始值,并使用指定的函数处理结果值。比第二个方法多了一个委托

Func<TAccumulate, TResult> resultSelector

然后我们来使用该方法

str = lst.Aggregate(
                "我的输出结果",
            (first, second) => $"first 哈哈哈 second ",
            end=> end +"我是尾巴"
    );

然后我们来转换一下,方便大家理解,

str = (lst.Aggregate(
             "我的输出结果",
         (first, second) => $"first 哈哈哈 second "

         ))+ "我是尾巴";

为什么我这里加了一个大括号,因为后面这个委托是对结果值进行一个整体处理,可能这样举例不太恰当,如果我们返回的结果是小写字母。我们就可以对整体结果进行一个小写转大写的操作

这样可能理解就更深刻了

str = lst.Aggregate(
                "我的输出结果",
            (first, second) => $"first 哈哈哈 second ",
            end=> end.ToUpper()
            );
            
            str = (lst.Aggregate(
             "我的输出结果",
         (first, second) => $"first 哈哈哈 second "

         )).ToUpper();

然后它的源码实现也比较简单了,就比第二个方法多了一个对结果值的处理,修改一下上面的源码

public static TResult Aggregate<TSource, TAccumulate, TResult>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func, Func<TAccumulate, TResult> resultSelector)
        
            if (source == null)
            
                throw Error.ArgumentNull("source");
            

            if (func == null)
            
                throw Error.ArgumentNull("func");
            

            if (resultSelector == null)
            
                throw Error.ArgumentNull("resultSelector");
            

            TAccumulate val = seed;
            foreach (TSource item in source)
            
                val = func(val, item);
            

            return resultSelector(val);
        

我们发现Aggregate只实现了

  • Aggregate(IEnumerable, Func<TSource,TSource,TSource>) 对序列应用累加器函数。

  • Aggregate<TSource,TAccumulate>(IEnumerable, TAccumulate, Func<TAccumulate,TSource,TAccumulate>) 对序列应用累加器函数。将指定的种子值用作累加器初始值。

  • Aggregate<TSource,TAccumulate,TResult>(IEnumerable, TAccumulate, Func<TAccumulate,TSource,TAccumulate>, Func<TAccumulate,TResult>)对序列应用累加器函数。将指定的种子值用作累加器的初始值,并使用指定的函数选择结果值

并没有实现 :对序列应用累加器函数,不使用指定种子的累加器初始值,且使用指定的函数选择结果值。那看了我的源码解析,是不是很容易实现了

public static TResult MyAggregate<TSource, TResult>(this IEnumerable<TSource> source,  Func<TSource, TSource, TSource> func, Func<TSource, TResult> resultSelector)
      

          if (source == null)
          
              throw new Exception("source is null");
          

          if (func == null)
          
              throw new Exception("func is null");
          

          using (IEnumerator<TSource> enumerator = source.GetEnumerator())
          
              if (!enumerator.MoveNext())
              
                  throw new Exception("enumerator is null");
              

              TSource val = enumerator.Current;
              while (enumerator.MoveNext())
              
                  val = func(val, enumerator.Current);
              

              return resultSelector(val);
          
      

如果还不明白对序列应用累加器函数。并使用指定的函数选择结果值,建议大家再看一遍.

最后大家如果喜欢我的文章,还麻烦给个关注并点个赞, 希望net生态圈越来越好!

以上是关于C# Linq源码解析之Aggregate的主要内容,如果未能解决你的问题,请参考以下文章

c# MoreLinq 之 Aggregate

C#之linq

细说Linq之Aggregate

C#-Linq源码解析之Average

C#-Linq源码解析之Any

C#-Linq源码解析之Concat