65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (10投票s)

2012 年 5 月 8 日

CPOL

4分钟阅读

viewsIcon

33369

downloadIcon

512

一个针对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; }); 

动机 

想象以下数据结构:

Class diagram with the following relations: (Vehicle <- Ship, (Car <- Truck))

还有一个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>接口的扩展方法。该方法接受两个泛型类型参数(TT2),其中T是基本集合元素的类型(例如Vehicle),而T2T的派生类型(例如Car),将用于过滤对其执行foreach操作的元素。该方法还接受两个参数,第一个是对调用该方法的集合的引用。另一个是Action<T2>委托,这是将对类型为T2的集合元素执行的操作。

调用该方法

可以使用以下方式调用扩展方法,使用前面的CarVehicle类:

Vehicles.ForEach<Vehicle, Car>(c => c.Print());

如果可以通过调用确定两个泛型类型的类型,那么就可以省略显式列出它们(<Vehicle, Car>)。做到这一点的一种方法是指定c lambda参数的类型:

Vehicles.ForEach((Car c) => c.Print());

在这种情况下,编译器将能够确定类型,因为TVehicles集合的类型给出,并且我们在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;
}  

结论  

感谢您的阅读,希望您喜欢,欢迎评论。

© . All rights reserved.