Introduction
There are many "missing" extensions of LINQ to Objects. You can look them up with LINQ Extensions Library or MoreLinq library, but with this article I would like to focus solely on ForEach extension. I believe that ForEach extension is the most missed feature from LINQ to Objects. While other extensions are nice-to-have and sometimes you use them and most of the time you don't, the ForEach extension is essential because foreach statements are intrinsic to C# code.
ForEach extension consists of just a simple foreach statement. The extension iterates over the elements of the collection and perform an action on each one of them. However, the benefit of this code being wrapped in an extension is that it can be written in a Lambda notation and can be chained with other LINQ expressions. If you just need the straightforward ForEach extension, you can NuGet it from the libraries I mentioned before. What I want to do in this article is to extend it a little bit further to accommodate more complex user-case scenarios.
ForEach
Our starting point is the basic and most simple version of ForEach extension. You can also take a look at the code in LINQ Extensions Library's ForEach.
// ForEach
public static IEnumerable ForEach(this IEnumerable source,
Action action)
{
foreach (TSource item in source)
action(item);
return source;
}
// Foreach with index
public static IEnumerable ForEach(this IEnumerable source,
Action action)
{
int index = 0;
foreach (TSource item in source)
action(item, index++);
return source;
}
These are the obvious extensions. The first extension performs an action on each item. The second extension also passes the item's index in the source collection.
int[] arr = new int[] { 0, 1, 2, 3, 4, 5 };
arr.ForEach(x => { Console.WriteLine("item " + x); });
arr.ForEach((x, index) => { Console.WriteLine("item " + x + ", index " + index); });
ForEach with previous items
Consider a situation in which you would like to iterate over the collection but would also like to have a reference to the previous item or the 2 previous items. Of course you can generalize this situation to any number of previous items but I think a real-life scenarios would beg no more than 2 items at most.
// Foreach with index and previous item
public static IEnumerable ForEach(this IEnumerable source,
Action action)
{
int index = 0;
TSource previousItem = default(TSource);
foreach (TSource item in source)
{
action(item, index++, previousItem);
previousItem = item;
}
return source;
}
// Foreach with index and 2 previous items
public static IEnumerable ForEach(this IEnumerable source,
Action action)
{
int index = 0;
TSource previousItem1 = default(TSource);
TSource previousItem2 = default(TSource);
foreach (TSource item in source)
{
action(item, index++, previousItem1, previousItem2);
previousItem2 = previousItem1;
previousItem1 = item;
}
return source;
}
When there are no previous items (start of the collection), the extension method pass along the default value of the source type.
int[] arr = new int[] { 0, 1, 2, 3, 4, 5 };
// ForEach with index and previous item
arr.ForEach((x, index, previousItem) =>
{
Console.WriteLine("previous item " + previousItem);
});
// ForEach with index and 2 previous items
arr.ForEach((x, index, previousItem1, previousItem2) =>
{
Console.WriteLine(
"previous item #1 " + previousItem1 +
", previous item #2 " + previousItem2
);
});
Breakable ForEach
A foreach statement is a loop which can be break out of by using the break statement. I wanted to mimic this behavior by letting the extension method to know when to continue and when to stop applying the action on the remaining items in the collection. The implementation is done by returning a boolean from the action. true means continue, false means break from the loop.
// Breakable ForEach
public static IEnumerable ForEach(this IEnumerable source,
Func action)
{
foreach (TSource item in source)
{
if (!action(item))
break;
}
return source;
}
This time the action argument is not an Action delegate but rather a Func delegate with boolean as its returning type. Breaking from the foreach loop doesn't impact the LINQ chaining because the source collection is returned as is. There are more overload versions of this Breakable ForEach, one with an index and 2 more with previous items.
int[] arr = new int[] { 0, 1, 2, 3, 4, 5 };
arr.ForEach(x =>
{
Console.WriteLine("item " + x);
if (x == 2)
return false; // break
return true; // continue
});
arr.ForEach((x, index) =>
{
Console.WriteLine("item " + x + ", index " + index);
if (index == 2)
return false; // break
return true; // continue
});
ForEach with previous results
We already saw that we can pass to the action one or two previous items from the collection. What if wanted to pass not the item but a value that is returned from the previous action? Consider our array of integers. A running sum for a given item in the array would be the current item + the result calculation of the running sum for the previous item.
// ForEach with index and previous result
public static IEnumerable ForEach(this IEnumerable source,
Func action)
{
int index = 0;
TResult previousResult = default(TResult);
foreach (TSource item in source)
previousResult = action(item, index++, previousResult);
return source;
}
The extension method requires both the type of the source collection and the type of the return value of the action.
int[] arr = new int[] { 0, 1, 2, 3, 4, 5 };
// ForEach with index and previous result
arr.ForEach((x, index, previousResult) =>
{
int result = 10 * x;
Console.WriteLine("result " + result + ", previous result " + previousResult);
return result;
});
// Running sum
arr.ForEach((x, index, previousResult) =>
{
int runningSum = previousResult + x;
Console.Write(runningSum + " ");
return runningSum;
});
Conclusion
I integrated this code with all my work projects and it has been helping me immensely. It's working seamlessly with any LINQ expression that I write. I hope it will help you too.
No comments:
Post a Comment