As apart of this series I will be investigating the Extension methods made available in the System.Linq namespace specifically for classes that implement IEnumerable. If you have any gems you have created for the extension method in question drop a comment below as I would love to see what people are coming up with.
The first extension method investigated is Aggregate the function documented as:
“Applies an accumulator function over a sequence.”
The following code is the simple program and data class I will be using to demonstrate the power of Aggregate! Please note if you are follow along line 19 is were the test cases are placed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class Entry { public int Id { get; set; } public string Name { get; set; } } class Program { static void Main(string[] args) { var items = new List<Entry>() { new Entry() {Id = 1, Name = "Horse"}, new Entry() {Id = 2, Name = "Dog"}, new Entry() {Id = 3, Name = "Cat"}, new Entry() {Id = 4, Name = "Pig"} }; // This is were our test cases go! Console.ReadLine(); } } |
Test case 1: Aggregate<(Of <(TSource>)>)(IEnumerable<(Of <(TSource>)>), Func<(Of <(TSource, TSource, TSource>)>))
First of all TSource is the strongly typed class the Enumeration contains so if your using List<string> your TSource is a string or if your using a List<MyObject> your TSource is a MyObject. The second important part is Func<(Of <(TSource, TSource, TSource>)> this says that aggregate will take a delegate that provides two TSources and returns one TSource following is an example of such a function:
Func<string, string, string> stupidFunction = (a, b) => b;
This really simple example takes two strings and returns one. Now we will look at the simplest of all 3 overloads available the only requirement is a lambda expression that returns the same type as the strongly typed Enumeration provides identified as TSource in the method signature.
19 20 21 22 23 24 25 | var entry = items.Aggregate((a, b) => { a.Name += b.Name; return a; }); Console.WriteLine(entry.Name); Console.WriteLine(items[0].Name); |
HorseDogCatPig
HorseDogCatPig
As you can see we have modified the Entry with the Id of 1 to contain an Aggregate of all the names, to avoid changing the first Entry we must introduce the idea of a Seed.
Test case 2: Aggregate<(Of <(TSource, TAccumulate>)>)(IEnumerable<(Of <(TSource>)>), TAccumulate, Func<(Of <(TAccumulate, TSource, TAccumulate>)>))
The seed we pass in will be the object we recieve as the result of calling Aggregate the same instance is passed into every call to the expression. The following uses the aggregate without modifying the Enumeration.
19 20 21 22 23 24 25 | var entry = items.Aggregate(new Entry(), (a, b) => { a.Name += b.Name; return a; }); Console.WriteLine(entry.Name); Console.WriteLine(items[0].Name); |
HorseDogCatPig
Horse
Another neat trick to try with Aggregate is to pass in a different kind of seed like. While the change is very suttle it has the effect of changing the return type to a StringBuilder and no longer an Entry so we must now call ToString instead of Name.
19 20 21 | var entry = items.Aggregate(new StringBuilder(), (a, b) => a.Append(b.Name) ); Console.WriteLine(entry.ToString()); Console.WriteLine(items[0].Name); |
HorseDogCatPig
Horse
Test case 3: Aggregate<(Of <(TSource, TAccumulate, TResult>)>)(IEnumerable<(Of <(TSource>)>), TAccumulate, Func<(Of <(TAccumulate, TSource, TAccumulate>)>), Func<(Of <(TAccumulate, TResult>)>))
The last overload gives you one more expression to play with which is run once all the items have been enumerated over, how you want to use this will depend on what your doing but the following just appends “. DONE!” to the end of the StringBuilder.
19 20 21 | var entry = items.Aggregate(new StringBuilder(), (a, b) => a.Append(b.Name), x => x.Append(". DONE!")); Console.WriteLine(entry.ToString()); Console.WriteLine(items[0].Name); |
HorseDogCatPig. DONE!
Horse
Test case 4: Real life scenario
Goal: Add up all the orders and provide a rounded total.
class Order { public double Total { get; set; } } class Program { static void Main(string[] args) { var orders = new List<Order>() { new Order() {Total = 123.23}, new Order() {Total = 512}, new Order() {Total = 123.23}, new Order() {Total = 5172.34}, new Order() {Total = 647.518}, new Order() {Total = 368.180}, new Order() {Total = 12.84}, new Order() {Total = 947.25}, }; var roundedGrandTotal = orders.Aggregate(new double(), (total, order) => total += order.Total, (total) => Math.Round(total, 2)); Console.WriteLine("{0:c}",roundedGrandTotal); Console.ReadLine(); } }
Result
$7,906.59
And thats it! I hope I have shed light on this extremely useful extension method which can be used for anything you dream up. This is my first article on .NET for this blog so please leave some feedback. System.Linq.Enumerable.All- Better Know an Extension Method Part 2