使用列表的多态性

本文关键字:多态性 列表 | 更新日期: 2023-09-27 18:03:44

我有一个对象的继承结构,有点像下面:

public class A { }
public class B : A { }
public class C : B { }

理想情况下,我希望能够将A, BCList传递给这样的单一方法:

private void Method(List<A> foos) { /* Method Implementation */ }
B toBee = new B();
B notToBee = new B();
List<B> hive = new List<B> { toBee, notToBee };
// Call Method() with a inherited type.  This throws a COMPILER ERROR 
// because although B is an A, a List<B> is NOT a List<A>.
Method(hive);

我想想出一种方法来获得相同的功能,尽可能少的代码重复。

我能想到的最好的方法是创建包装器方法,接受各种类型的列表,然后循环通过传递的列表调用相同的方法;最后使用多态性对我有利:

private void Method(List<A> foos) { foreach (var foo in foos) Bar(foo); }
private void Method(List<B> foos) { foreach (var foo in foos) Bar(foo); }
private void Method(List<C> foos) { foreach (var foo in foos) Bar(foo); }
// An A, B or C object can be passed to this method thanks to polymorphism
private void Bar(A ayy) { /* Method Implementation */ }

Bus如您所见,我已经将该方法复制粘贴了三次,只更改了列表泛型中包含的类型。我已经开始相信,任何时候你开始复制和粘贴代码,有一个更好的方法去做……但我似乎想不出一个。

我怎样才能完成这样的壮举而不需要复制和粘贴?

使用列表的多态性

创建一个泛型方法:

private void Method<T>(List<T> foos) 

,所以你将能够使用它的各种List<T>。您还可以使用泛型约束缩小方法接受的参数列表,以便仅处理A子类:

private void Method<T>(List<T> foos) 
    where T : A

那么您确定foos的每个元素都可以用作A的实例:

private void Method<T>(List<T> foos) 
    where T : A
{
    foreach (var foo in foos)
    {
        var fooA = foo as A;
        // fooA != null always (if foo wasn't null already)
        Bar(fooA);
    }
}

正如Lucas Trzesniewski在他的回答中指出的那样,它甚至更好如果不需要修改集合,则使用IEnumerable<T>。它是协变的所以你不会遇到你刚才描述的问题

你需要协方差

如果你可以这样写:

private void Method(List<A> foos) { foreach (var foo in foos) Bar(foo); }
private void Method(List<B> foos) { foreach (var foo in foos) Bar(foo); }
private void Method(List<C> foos) { foreach (var foo in foos) Bar(foo); }
// An A, B or C object can be passed to this method thanks to polymorphism
private void Bar(A ayy) { /* Method Implementation */ }

那么它只意味着 List<T>首先是错误的参数类型
最好使用IEnumerable<out T>,有两个原因:

  • 它是协变,所以IEnumerable<C>IEnumerable<A>
  • 这是方法中唯一需要的东西,因为你只做了一个迭代。你可以传入HashSet<B>, LinkedList<A>C[],它仍然会工作得很好。
private void Method(IEnumerable<A> foos)
{
    foreach (var foo in foos)
        Whatever(foo);
}

List<T>是一个机制 -它将T引用(或值类型的值)存储在数组的连续内存中。不需要需要这个属性。

相比之下,IEnumerable<out T>是一个合约。它只说你可以枚举一个序列,这就是你在这个例子中所需要的。

你可以使用其他协变接口类型,如IReadOnlyCollection<out T>(如果你需要预先知道项目计数或如果你必须枚举多次)或IReadOnlyList<out T>(如果你需要项目索引)。

请记住,在大多数情况下,最好将接口类型作为参数,因为它允许您在需要时切换底层实现。

但是如果你真的要修改列表,那么协方差是不够的。在这种情况下,使用BartoszKP的解决方案,但是,嘿,您仍然可以使用IList<T>而不是List<T>:-)