对 IEnumerable 集合中派生类型的元素进行 ForEach 操作






4.87/5 (10投票s)
一个针对IEnumerable接口的扩展方法,用于对属于T的派生类型的每个元素执行操作。
介绍
本文将提供一个针对IEnumerable<T>
和IEnumerable
接口的扩展方法,用于对属于T的派生类型的每个元素执行操作。使用此方法,您只需一行代码即可在集合的一部分上调用操作。
使用代码
为了访问此扩展方法,请在您的程序中包含以下using指令:
using Rankep.CollectionExtensions;
您需要一个实现IEnumerable<T>
接口的集合(例如:List<T>
),该方法将作用于该集合。示例:
IEnumerable<T> Vehicles = new List();
现在可以使用以下代码(看起来像List<T>
类的ForEach
方法,但它将使用我们的扩展方法)来遍历整个集合,并对每个元素调用Print
方法(由Vehicle
类定义):
Vehicles.ForEach(v => v.Print());
可以通过两种方式遍历具有给定(派生)类型的元素(在本例中为Car
):
Vehicles.ForEach<Vehicle, Car>(c => c.Print());
或
Vehicles.ForEach((Car c) => c.Print());
两种方式都使用相同的ForEach<T, T2>
扩展方法,但在第二个示例中,通过指定lambda参数的类型,可以确定类型而无需显式定义它们。
还有一个适用于实现非泛型IEnumerable
接口的集合的第三个扩展方法。它的用法与前面提到的泛型接口的方法相同。
这种集合的一个例子是WPF Grid
元素的Children
属性。它有一个名为UIElementCollection
的类型,该类型实现了IEnumerable
接口。
要更改名为grid的Grid中所有是Ellipse的子元素的大小,我们可以通过以下方式使用第三个扩展方法:
grid.Children.ForEach((Ellipse c) => { c.Width = 30; c.Height = 30; });
动机
想象以下数据结构:
还有一个Vehicles
变量,它是一个Vehicle
元素的集合。(它实现了IEnumerable<Vehicle>
接口)。
如果您想遍历这些元素并对它们调用Print方法,那么您可以使用foreach语句:
foreach (Vehicle item in Vehicles)
{
item.Print();
}
那么,如果您只想对具有给定派生类型的元素(例如Car)执行操作呢?
我们希望能够这样写:
Vehicles.ForEach((Car c) => c.Print());
解决方案
如何只遍历给定类型的元素?
第一个想法(至少是我的想法)是在foreach语句中将元素的类型指定为派生类型,并希望它能自动工作。好吧,它不行,foreach的工作方式不是这样的。foreach会尝试将集合中的每个元素强制转换为我们指定的类型,如果存在不属于该类型的项,则会导致InvalidCastException。
好的,在foreach中我们应该使用基类类型。如果我们检查循环体中当前元素是否是所需类型,并且只在它是时执行操作呢?这正是扩展方法将要做的事情。让我们来看一个关于前面Vehicle <- Car示例的样本。
foreach (Vehicle item in collection)
{
if (item is Car)
{
action((Car)item);
}
}
在将其通用化之后,这是扩展方法的代码:
public static IEnumerable<T> ForEach<T, T2>(this IEnumerable<T> collection, Action<T2> action) where T2 : T
{
//The action can not be null
if (action == null)
throw new ArgumentNullException("action");
//Loop through the collection
foreach (var item in collection)
{
//If the current element belongs to the type on which the action should be performed
if (item is T2)
{
//Then perform the action after casting the item to the derived type
action((T2)item);
}
}
//Return the original collection
return collection;
}
这是一个针对IEnumerable<T>
接口的扩展方法。该方法接受两个泛型类型参数(T
和T2
),其中T
是基本集合元素的类型(例如Vehicle
),而T2
是T
的派生类型(例如Car
),将用于过滤对其执行foreach
操作的元素。该方法还接受两个参数,第一个是对调用该方法的集合的引用。另一个是Action<T2>
委托,这是将对类型为T2
的集合元素执行的操作。
调用该方法
可以使用以下方式调用扩展方法,使用前面的Car
和Vehicle
类:
Vehicles.ForEach<Vehicle, Car>(c => c.Print());
如果可以通过调用确定两个泛型类型的类型,那么就可以省略显式列出它们(<Vehicle, Car>
)。做到这一点的一种方法是指定c
lambda参数的类型:
Vehicles.ForEach((Car c) => c.Print());
在这种情况下,编译器将能够确定类型,因为T
由Vehicles
集合的类型给出,并且我们在lambda表达式的左侧显式指定了T2
。
用于整个集合的扩展方法
如果我们想使用此扩展方法对整个集合执行操作,那么我们将不得不指定T2
的类型,在这种情况下,它将与T
相同。为了允许省略此冗余信息,我们可以定义一个新的扩展方法,它只有一个泛型参数并简单地调用另一个扩展方法:
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> collection, Action<T> action)
{
//Call the other extension method using the one generic type for both generic parameters
return collection.ForEach(action);
}
扩展非泛型IEnumerable接口
上面显示的两个方法都扩展了泛型IEnumerable<T>
接口,但并非所有集合都是泛型的。为了能够将这些方法用于非泛型集合,还将提供另一个扩展方法,该方法扩展IEnumerable
接口。
该方法将只调用第一个扩展方法,然后返回原始集合。
为了调用该方法,我们必须将非泛型集合转换为泛型集合。为此,我们将使用Cast<T>()方法,并利用一切都继承自object
类这一事实。
public static IEnumerable ForEach<T>(this IEnumerable collection, Action<T> action)
{
//Cast the collection to an IEnumerable<T> and call the already defined extension method
collection.Cast<object>().ForEach<object, T>(action);
return collection;
}
结论
感谢您的阅读,希望您喜欢,欢迎评论。