Zac*_*ten 4 c# generics polymorphism
我有一个对象的继承结构,有点像下面这样:
public class A { }
public class B : A { }
public class C : B { }
Run Code Online (Sandbox Code Playgroud)
理想情况下,我希望能够传递List的A,B或C以这样一个方法:
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>,如果你不需要修改集合,那就更好了.这是协变的,所以你不会遇到你所描述的问题.
你需要协方差.
如果你可以写下面的内容:
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>:-)