Click here to Skip to main content
14,883,453 members
Articles / Programming Languages / C#
Article
Posted 6 Mar 2018

Tagged as

Stats

5.9K views
51 downloads
4 bookmarked

Aggregate, the True Aggregator LinQ Operator

Rate me:
Please Sign up or sign in to vote.
3.92/5 (6 votes)
6 Mar 2018CPOL3 min read
Review the LinQ Aggregators operators and show all operators can build with the Aggregate operator.

Introduction

Aggregate LinQ operator is an unknown in the LinQs worlds. The goal of the project is review the LinQ Aggregators operators (Count, LongCount, Min, Max, Sum and Average), and show all operators can build with the Aggregate operator.

Index

  • Count and LongCount
  • Sum
  • Min y Max
  • Average
  • Aggregate

Previous operators Aggregate implementations:

  • Sum
  • Min
  • Max
  • Average

Count and LongCount

The extension methods Count and LongCount, return the number of elements of sequence. The difference between Count and LongCount is the return type. Count operator returns an int (System.Int32) type, and your limit is 2.147.483.647 elements. LongCount operator returns a Long (System.Int64) type, and your limit is much bigger than Count Operator 9.223.372.036.854.775.807 elements. The Count operator should be sufficient for natural cases of day to day.

Signatures:

C#
public static int Count<TSource>(this IEnumerable<TSource> source);
public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
 
public static long LongCount<TSource>(this IEnumerable<TSource> source);
public static long LongCount<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);

Both of them have two overloads. The first overload is a simple method and counts all elements of sequence. The second overload has a predicate parameter (Func<TSource, bool>) for counting only a part of the sequence.

Examples:

C#
public static void CountAndLongCountTests()
{
    var elements = new List<Element>()
    {
        new Element{ ID = 0, Amount = 10000},
        new Element{ ID = 1, Amount =  1000},
        new Element{ ID = 2, Amount =  2000},
        new Element{ ID = 3, Amount =  3000}
    };
 
 
    var numSmall = elements.Count();
    var numBig   = elements.LongCount();
 
    var numSmallFiltered = elements.    Count(e => e.Amount >= 2000);
    var numBigFiltered   = elements.LongCount(e => e.Amount >= 2000);
 
 
    Console.WriteLine($"{nameof(numSmallFiltered)} type: {numSmallFiltered.GetType().Name}");
    Console.WriteLine($"{nameof(numBigFiltered)}   type: {numBigFiltered.GetType()  .Name}");
 
    Console.Read();
}

Console result:

Image 1

The result is the same, only change the datatype.

LongCont only should be used for very big collections.

Sum

The Sum extension method returns the sum of elements sequence. These elements can be null.

The Sum operator has 20 overloads in 2 groups.

The first group is formed for methods with only one parameter. This parameter is always numeric and is the same type that the source collection type and the return type.

Signatures group 1:

C#
public static long     Sum(this IEnumerable<long>     source);
public static long?    Sum(this IEnumerable<long?>    source);
public static float?   Sum(this IEnumerable<float?>   source);
public static double   Sum(this IEnumerable<double>   source);
public static double?  Sum(this IEnumerable<double?>  source);
public static decimal? Sum(this IEnumerable<decimal?> source);
public static decimal  Sum(this IEnumerable<decimal>  source);
public static float    Sum(this IEnumerable<float>    source);
public static int?     Sum(this IEnumerable<int?>     source);
public static int      Sum(this IEnumerable<int>      source);

Examples:

C#
public static void SumTests1()
{
    var elements = new List<Element>()
    {
        new Element{ ID = 0, Amount = 10000},
        new Element{ ID = 1, Amount =  1000},
        new Element{ ID = 2, Amount =  2000},
        new Element{ ID = 3, Amount =  3000}
    };
 
    int[] numbers = new int[] { 1000, 2000, 3000 };
 
 
    int resultNumbers = numbers.Sum();
 
    var sumAmount = elements.Select(a => a.Amount).Sum();
 
    Console.WriteLine($"{nameof(resultNumbers)}   = {resultNumbers}");
    Console.WriteLine($"{nameof(sumAmount)}       = {sumAmount}");
 
    Console.Read();
}

Console result:

Image 2

The calls do not contain a parameter, because it is an extension method parameter.

The second group has 10 methods too, for this case, they are generics methods with two parameters. The first parameter is the same as that first group. The second selector parameter equals to Select extension method.

Signatures group 2:

C#
public static float?   Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, float?>   selector);
public static double?  Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, double?>  selector);
public static decimal  Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal>  selector);
public static decimal? Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal?> selector);
public static double   Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, double>   selector);
public static int?     Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, int?>     selector);
public static long?    Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, long?>    selector);
public static float    Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, float>    selector);
public static long     Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, long>     selector);
public static int      Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, int>      selector);

Examples:

C#
public static void SumTests2()
{
    var elements = new List<Element>()
    {
        new Element { ID = 0, Amount = 10000},
        new Element { ID = 1, Amount =  1000},
        new Element { ID = 2, Amount =  2000},
        new Element { ID = 3, Amount =  3000}
    };
 
    var sumAmount = elements.Sum(a => a.Amount);
 
    Console.WriteLine($"{nameof(sumAmount)} = {sumAmount}");
 
    Console.Read();
}

We add a Func<Element, int> parameter.

Console result:

Image 3

For the null values, the process will understand, null = 0.

C#
public static void SumTestsNull()
{
    var elements = new List<Element>()
    {
        new Element { ID = 0, Amount = null},
        new Element { ID = 1, Amount = null},
        new Element { ID = 2, Amount = null},
        new Element { ID = 3, Amount = null}
    };
 
    var sumAmount = elements.Sum(a => a.Amount);
 
    Console.WriteLine($"{nameof(sumAmount)} = {sumAmount}");
 
    Console.Read();
}

Console result:

Image 4

Min and Max

The Min and Max operators have the same peculiarities as Sum operator, but with a different objective. Max gets the highest value in the sequence and Min gets the minimum value.

Signatures:

C#
public static double?   Max(this IEnumerable<double?>  source);
public static double    Max(this IEnumerable<double>   source);
public static long?     Max(this IEnumerable<long?>    source);
public static long      Max(this IEnumerable<long>     source);
public static int?      Max(this IEnumerable<int?>     source);
public static float     Max(this IEnumerable<float>    source);
public static float?    Max(this IEnumerable<float?>   source);
public static int       Max(this IEnumerable<int>      source);
public decimal          Max(this IEnumerable<decimal>  source);
public decimal?         Max(this IEnumerable<decimal?> source);
 
public static long?     Min(this IEnumerable<long?>    source);
public static int?      Min(this IEnumerable<int?>     source);
public static int       Min(this IEnumerable<int>      source);
public static float?    Min(this IEnumerable<float?>   source);
public static double    Min(this IEnumerable<double>   source);
public static double?   Min(this IEnumerable<double?>  source);
public static decimal   Min(this IEnumerable<decimal>  source);
public static decimal?  Min(this IEnumerable<decimal?> source);
public static long      Min(this IEnumerable<long>     source);
public static float     Min(this IEnumerable<float>    source);
 
 
public static int       Max<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, int>      selector);
public static int?      Max<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, int?>     selector);
public static long      Max<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, long>     selector);
public static long?     Max<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, long?>    selector);
public static float     Max<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, float>    selector);
public static float?    Max<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, float?>   selector);
public static double    Max<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, double>   selector);
public static double?   Max<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, double?>  selector);
public static decimal   Max<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, decimal>  selector);
public static decimal?  Max<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, decimal?> selector);
 
public static long?     Min<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, long?>    selector);
public static decimal?  Min<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, decimal?> selector);
public static decimal   Min<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, decimal>  selector);
public static float     Min<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, float>    selector);
public static double?   Min<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, double?>  selector);
public static int?      Min<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, int?>     selector);
public static int       Min<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, int>      selector);
public static float?    Min<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, float?>   selector);
public static long      Min<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, long>     selector);
public static double    Min<TSource>(this IEnumerable<TSource> source, 
                        Func<TSource, double>   selector);

Simple example:

C#
public static void MinMaxTests()
{
    int[] numbers = new int[] { 1000, 2000, 3000 };
 
    int maxNumbers = numbers.Max();
    int minNumbers = numbers.Min();
 
 
    Console.WriteLine($"{nameof(maxNumbers) } = {maxNumbers }");
    Console.WriteLine($"{nameof(minNumbers) } = {minNumbers }");
 
    Console.Read();
}

Result:

Image 5

Complex example:

C#
public static void MinMaxTests2()
{
    var elements = new List<Element>()
    {
        new Element { ID = 0, Amount = 10000},
        new Element { ID = 1, Amount =  1000},
        new Element { ID = 2, Amount =  2000},
        new Element { ID = 3, Amount =  3000}
    };
 
    var maxAmountForSelect = elements.Max(a => a.Amount);
    var minAmountForSelect = elements.Min(a => a.Amount);
 
    var maxAmountForField = elements.Max(a => a.Amount);
    var minAmountForField = elements.Min(a => a.Amount);
 
    Console.WriteLine($"{nameof(maxAmountForSelect)} = {maxAmountForSelect}");
    Console.WriteLine($"{nameof(minAmountForSelect)} = {minAmountForSelect}");
    Console.WriteLine($"{nameof(maxAmountForField) } = {maxAmountForField }");
    Console.WriteLine($"{nameof(minAmountForField) } = {minAmountForField }");
 
    Console.Read();
}

Result:

Image 6

Average

Same formula and same behavior and treatment as for his brothers, Sum, Max and Min, but to find the average.

In this case, the return types are always types with decimal parts as decimal or double.

Signatures:

C#
public static float    Average(this IEnumerable<float>    source);
public static double?  Average(this IEnumerable<long?>    source);
public static float?   Average(this IEnumerable<float?>   source);
public static double   Average(this IEnumerable<double>   source);
public static double   Average(this IEnumerable<int>      source);
public static decimal  Average(this IEnumerable<decimal>  source);
public static decimal? Average(this IEnumerable<decimal?> source);
public static double   Average(this IEnumerable<long>     source);
public static double?  Average(this IEnumerable<double?>  source);
public static double?  Average(this IEnumerable<int?>     source);

public static decimal  Average<TSource>(this IEnumerable<TSource> source, 
                       Func<TSource, decimal>  selector);
public static decimal? Average<TSource>(this IEnumerable<TSource> source, 
                       Func<TSource, decimal?> selector);
public static double?  Average<TSource>(this IEnumerable<TSource> source, 
                       Func<TSource, int?>     selector);
public static double   Average<TSource>(this IEnumerable<TSource> source, 
                       Func<TSource, long>     selector);
public static double?  Average<TSource>(this IEnumerable<TSource> source, 
                       Func<TSource, long?>    selector);
public static float    Average<TSource>(this IEnumerable<TSource> source, 
                       Func<TSource, float>    selector);
public static float?   Average<TSource>(this IEnumerable<TSource> source, 
                       Func<TSource, float?>   selector);
public static double   Average<TSource>(this IEnumerable<TSource> source, 
                       Func<TSource, double>   selector);
public static double?  Average<TSource>(this IEnumerable<TSource> source, 
                       Func<TSource, double?>  selector);
public static double   Average<TSource>(this IEnumerable<TSource> source, 
                       Func<TSource, int>      selector);

Simple example:

C#
public static void Average1()
{
    int[] numbers = new int[] { 1000, 2000, 3000 };
 
    var mediaNumbers = numbers.Average();
 
    Console.WriteLine($"{nameof(mediaNumbers) } = {mediaNumbers }");
 
    Console.Read();
}

Result:

Image 7

Complex example:

C#
public static void Average2()
{
    var elements = new List<Element>()
    {
        new Element { ID = 0, Amount = 10000},
        new Element { ID = 1, Amount =  1000},
        new Element { ID = 2, Amount =  2000},
        new Element { ID = 3, Amount =  3000}
    };
 
    var mediaForSelect = elements.Max(a => a.Amount);
 
    var mediaForField = elements.Max(a => a.Amount);
 
 
    Console.WriteLine($"{nameof(mediaForSelect)} = {mediaForSelect}");
    Console.WriteLine($"{nameof(mediaForField) } = {mediaForField}");
 
 
    Console.Read();
}

Result:

Image 8

Aggregate

In a nutshell, the extension method Aggregate as is used for accumulates function.

Aggregate has three signatures:

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

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

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

First Signature

This is the simplest signature. The Aggregate operator calls the delegate anonymous Func<TAccumulate, TSource, TAccumulate> func (accumulate function), foreach elements in a collection.

C#
public static void Aggregate1()
{
    string[] names = { "Mike", "Julie", "John", "Laura", "Other name" };
 
    string result = names.Aggregate((resultAcum, next) => 
                    resultAcum += string.Format("\r\n{0:-16}", next));
 
    /// This is the translate in simple bucle
    //string result = string.Empty;
 
    //foreach (var next in nombres)
    //{
    //    result += string.Format("\r\n{0:-16}", next);
    //}
 
 
    Console.WriteLine(result);
 
    Console.Read();
}

Result:

Image 9

Second Signature

The second signature is the same as the first but the second signature has a new parameter of TSource type. This parameter shows the initial accumulate value.

Example:

C#
public static void Aggregate2()
{
    string[] names = { "Mike", "Julie", "John", "Laura", "Other name" };
 
    string result = names.Aggregate("Inital Date", 
                    (resultAcum, next) => resultAcum += string.Format("\r\n{0:-16}", next));
 
 
    /// This is the translate in simple bucle
    //string result = "Initial Date";
 
    //foreach (var next in nombres)
    //{
    //    result += string.Format("\r\n{0:-16}", next);
    //}
 
 
    Console.WriteLine(result);
 
    Console.Read();
}

Result:

Image 10

Third Signature

The last signature has the same characteristics as the previous, but adds a new parameter for configuring the output format.

Simple example:

C#
public static void Aggregate3()
{
    string[] names = { "Mike", "Julie", "John", "Laura", "Other name" };
 
    string result = names.Aggregate("Initial Date", 
                    (resultAcum, next) => resultAcum += string.Format("\r\n{0:-16}", next));
 
    string resultado = names.Aggregate("Inital Date",
                                (resultAcum, next) => resultAcum += string.Format("\r\n{0:-16}", next),
                                a => $"{a} \r\n --> Total characters:  {a.Length} "
                                );
 
    Console.WriteLine(resultado);
 
    Console.Read();
}

Result:

Image 11

Complex example:

C#
public static void Aggregate4()
{
    string[] names = { "Mike", "Julie", "John", "Laura", "Other name" };
 
    var separador = new string[] { "\r\n" };
 
    var result = names.Aggregate("Inital Date",
                  (resultAcum, next) => resultAcum += string.Format("\r\n{0:-16}", next),
                  // We create an Anonymous type foreach element
                  a => a.Split(separador, StringSplitOptions.None).Select
                       (b => new { Dato = b, Len = b.Length })
                  );
 
    result.ToList().ForEach(r => Console.WriteLine(r));
 
    Console.Read();
}

Result:

Image 12

It’s very important to see the objective of the last parameter. In this example, we split the name strings for creating a new anonymous object with two properties: Dato = (string name) and Len = (length of dato).

Recreate All Operators Only with Aggregate

Now we will demonstrate that the Aggregate is the most important operator, and we will demonstrate that with this operator we can be creating all previous operators.

Sum

C#
public static void Sum_Aggregate()
{ 
    int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    var sum = numbers.Aggregate((total, next) => total += next);
    Console.WriteLine $"The {nameof(sum)} value is {sum}");
    Console.Read();
}

Result.

Image 13

Min

C#
public static void Min_Aggregate()
{ 
    int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
 
    var min = numbers.Aggregate((total, next) => next < total ? next : total);
 
    Console.WriteLine($"The {nameof(min)} value is {min}");
 
    Console.Read();
}

Result:

Image 14

Max

C#
public static void Max_Aggregate()
{
 
    int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
 
    var max = numbers.Aggregate((total, next) => next > total ? next : total);
 
    Console.WriteLine($"The {nameof(max)} value is {max}");
 
    Console.Read();
}

Result:

Image 15

Average

C#
public static void Average_Aggregate()
{ 
    int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
 
    var average = numbers.Aggregate(
                                0,
                                (total, next) => total += next,
                                a => decimal.Parse(a.ToString()) / 
                                     decimal.Parse(numbers.Count().ToString())
                            );
 
    Console.WriteLine($"The {nameof(average)} value is {average}");
 
    Console.Read();
}

Result:

Image 16

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Juan Francisco Morales Larios
Software Developer (Senior) Cecabank
Spain Spain
MVP C# Corner 2017

MAP Microsoft Active Professional 2014

MCPD - Designing and Developing Windows Applications .NET Framework 4
MCTS - Windows Applications Development .NET Framework 4
MCTS - Accessing Data Development .NET Framework 4
MCTS - WCF Development .NET Framework 4

Comments and Discussions

 
-- There are no messages in this forum --