与列表的多态性

Zac*_*ten 4 c# generics polymorphism

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

public class A { }
public class B : A { }
public class C : B { }
Run Code Online (Sandbox Code Playgroud)

理想情况下,我希望能够传递ListA,BC以这样一个方法:

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);
Run Code Online (Sandbox Code Playgroud)

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

我能想到的最好的方法是创建包含各种类型列表的包装器方法,然后遍历传递的列表来调用相同的方法; 最后使用多态对我有利:

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 */ }
Run Code Online (Sandbox Code Playgroud)

你可以看到总线,我已经三次复制和粘贴该方法,只更改列表通用中包含的类型.我开始相信,无论何时开始复制和粘贴代码,都有更好的方法来做到这一点......但我似乎无法想出一个.

如何在没有不受欢迎的复制和粘贴的情况下完成这样的壮举?

Bar*_*zKP 12

创建一个通用方法:

private void Method<T>(List<T> foos) 
Run Code Online (Sandbox Code Playgroud)

所以你可以用它来做各种各样的事情List<T>.您还可以A使用通用约束缩小方法的接受参数列表,以便仅处理子类:

private void Method<T>(List<T> foos) 
    where T : A
Run Code Online (Sandbox Code Playgroud)

然后你确定每个元素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);
    }
}
Run Code Online (Sandbox Code Playgroud)

正如Lucas Trzesniewski在他的回答中指出的那样IEnumerable<T>,如果你不需要修改集合,那就更好了.这是协变的,所以你不会遇到你所描述的问题.


Luc*_*ski 8

你需要协方差.

如果你可以写下面的内容:

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 */ }
Run Code Online (Sandbox Code Playgroud)

那么它首先只意味着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);
}
Run Code Online (Sandbox Code Playgroud)

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

相比之下,IEnumerable<out T>是一份合同.它只表示你可以枚举一个序列,在这种情况下你只需要它.

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

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

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