2015-07-20 26 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版卡住了。 –