2015-07-20 23 views
1

我有对象的继承结构,有点像以下:多态性与解释

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

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

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 */ } 

巴士,你可以看到,我已经从字面上复制并粘贴该方法三次,只改变包含在通用列表的类型。我开始相信,只要你开始复制和粘贴代码,有一种更好的方式来做到这一点......但我似乎无法提出一个。

我怎样才能做到无不良复制和粘贴这样的壮举?

回答

8

创建generic method

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

所以,你将能够使用它的各种List<T>。您也可以缩小所接受的参数列表的处理仅A子类的方法,用generic constraints

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>。它是协变的,所以你不会遇到你所描述的问题。

7

你需要covariance

如果你可以写:

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> :-)

+0

这是一个更好的解决方案! – BartoszKP

+1

@BartoszKP它取决于实施中的内容,如果列表需要修改,那么你的解决方案更好:-) –

+1

谢谢你的信息丰富的答案卢卡斯。不幸的是,它看起来像C#在.NET 4.0之前不支持泛型接口中的协变性,而我使用3.5版卡住了。 –