Introduction
This post will provide an extension method for the IEnumerable<T>
and IEnumerable
interface to perform an action on each element which belong to a derived type of T
. Using this method you can invoke actions on a part of a collection with just one line of code.
Using the code
In order to access the extension method include the following using directive in your program:
using Rankep.CollectionExtensions;
You will need a collection which implements the IEnumerable<T>
interface (for example: List<T>
) on which the method will act. Example:
IEnumerable<T> Vehicles = new List();
Now the following code can be used (it looks like the ForEach
method of the List<T>
class, but it will use our extension method) to loop through the whole collection, and call the Print
method (defined by the Vehicle
class) on each element:
Vehicles.ForEach(v => v.Print());
Looping through the elements which have a given (derived) type (Car
, in the example), can be done in two ways:
Vehicles.ForEach<Vehicle, Car>(c => c.Print());
or:
Vehicles.ForEach((Car c) => c.Print());
Both ways use the same ForEach<T, T2>
extension method, but in the second example the types can be determined without explicitly defining them, by specifying the type of the lambda parameter.
A third extension method is available for collections which implement the non-generic IEnumerable
interface. It can be used in the same way as the latter method for the generic interface.
An example for such collection would be the Children
property of a WPF Grid
element. It has a type called UIElementCollection
, which implements the IEnumerable
interface.
To change the size of all children elements of a Grid (called grid) that is an Ellipse, we can use the third extension method in the following way:
grid.Children.ForEach((Ellipse c) => { c.Width = 30; c.Height = 30; });
Motivation
Imagine the following data structure:
And a Vehicles
variable which is a collection of Vehicle
elements. (It implements the IEnumerable<Vehicle>
interface).
If you would like to loop through the elements and invoke the Print method on them, then you could use the foreach construct:
foreach (Vehicle item in Vehicles)
{
item.Print();
}
And what if you would like to perform an action only on those elements which share a given derived type (e.g. Car)?
We would like to be able to write something like this:
Vehicles.ForEach((Car c) => c.Print());
Solution
How to loop through only the elements of the given type?
The first idea would be (at least mine was) that in the foreach statement specify the type of the element as a derived type, and hope for it will automagically work. Well, it doesn’t, it is not how foreach works. Foreach tries to cast each element in the collection to the type we specified, which will result in an InvalidCastException if there is any item which doesn’t belong to that type.
Okay, in the foreach we should use the base type. What if we check in the loop body if the current element is of the desired type and only execute the action when it is? This is exactly what the extension method will do. Let’s see a sample for the previous Vehicle <- Car example.
foreach (Vehicle item in collection)
{
if (item is Car)
{
action((Car)item);
}
}
After generalizing it, here is the code of the extension method:
public static IEnumerable<T> ForEach<T, T2>(this IEnumerable<T> collection, Action<T2> action) where T2 : T
{
if (action == null)
throw new ArgumentNullException("action");
foreach (var item in collection)
{
if (item is T2)
{
action((T2)item);
}
}
return collection;
}
It’s an extension method to the IEnumerable<T>
interface. The method takes two generic type parameters (T
and T2
), from which T
is the type of the basic collection elements (e.g. Vehicle
), and T2
is a derived type of T
(e.g. Car
) that will be used to filter on which elements the foreach
action will be performed. The method also takes two parameters, the first will be the reference to the collection on which the method is invoked. The other will be an Action<T2>
delegate, which is the action that will be performed on the collection elements of type T2
.
Calling the method
The extension method can be invoked in the following way, using the previous Car
and Vehicle
classes:
Vehicles.ForEach<Vehicle, Car>(c => c.Print());
If the type of the two generic types can be determined by the call the then explicit listing of them (<Vehicle, Car>
) can be omitted. One way to do this is to specify the type of the c
lambda argument:
Vehicles.ForEach((Car c) => c.Print());
In this case the compiler will be able to determine the types, because T
is given by the type of the Vehicles
collection, and we explicitly specified T2
in the left side of the lambda expression.
Extension method for the whole collection
If we would like to use this extension method to perform an action on the whole collection, then we would have to specify the type of T2
, which in this case will be the same as T
. To allow omitting this redundant information we can define a new extension method which has only one generic parameter and simple calls the other extension method:
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> collection, Action<T> action)
{
return collection.ForEach(action);
}
Extending the non-generic IEnumerable interface
Both of the shown methods extend the generic IEnumerable<T>
interface, but not every collection is generic. To make it possible to use these methods on non-generic ones, one more extension method will be provided, which extends the IEnumerable
interface.
The method will only call the first extension method and then return the original collection.
In order to call that method we have to turn the non-generic collection into a generic one. To do this we will use the Cast<T>() method and the fact that everything inherits from the object
class.
public static IEnumerable ForEach<T>(this IEnumerable collection, Action<T> action)
{
collection.Cast<object>().ForEach<object, T>(action);
return collection;
}
Conclusion
Thank you for reading, I hope you enjoyed and feel free to comment.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.