2012-02-25 88 views
28

我寫了這個擴展方法(編譯):平展IEnumerable <IEnumerable <>>;瞭解泛型

public static IEnumerable<J> Flatten<T, J>(this IEnumerable<T> @this) 
              where T : IEnumerable<J> 
{ 
    foreach (T t in @this) 
     foreach (J j in t) 
      yield return j; 
} 

下面的代碼將導致編譯時錯誤(沒有合適的方法找到),爲什麼呢?

IEnumerable<IEnumerable<int>> foo = new int[2][]; 
var bar = foo.Flatten(); 

如果我實現擴展類似下面,我沒有得到任何編譯時錯誤:

public static IEnumerable<J> Flatten<J>(this IEnumerable<IEnumerable<J>> @this) 
{ 
    foreach (IEnumerable<J> js in @this) 
     foreach (J j in js) 
      yield return j; 
} 

編輯(2):這個問題我考慮的回答,但它提出了關於另外一個問題重載解析和類型約束。我把這個問題放在這裏:Why aren't type constraints part of the method signature?

+1

您的編輯不起作用,因爲您有太多的周圍的enumerable。 'foo.Flatten ,int>();'應該工作。 – dlev 2012-02-25 02:20:53

回答

65

首先,你不需要Flatten();該方法已存在,稱爲SelectMany()。您可以使用它像這樣:

IEnumerable<IEnumerable<int>> foo = new [] { new[] {1, 2}, new[] {3, 4} }; 
var bar = foo.SelectMany(x => x); // bar is {1, 2, 3, 4} 

其次,你的第一次嘗試不起作用,因爲通用的類型推斷工作的論點只是基於上述方法,該方法不相關的泛型約束。由於沒有直接使用J泛型參數的參數,類型推斷引擎無法猜測J應該是什麼,因此不認爲您的方法是候選者。

想知道SelectMany()如何解決這個問題,需要額外的Func<TSource, TResult>參數。這允許類型推理引擎確定兩種泛型類型,因爲它們都是基於提供給方法的參數。

+1

@Daryl:因爲它應該是'Flatten ,int>(foo)' – BrokenGlass 2012-02-25 02:23:13

+2

@Daryl通用約束不被視爲方法簽名的一部分;爲*方式更多*,請參閱此鏈接:http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx – dlev 2012-02-25 02:24:44

+1

@Daryl :不要 - 在這裏肯定有學習曲線,這遠遠不是C#最容易理解的方面。只是試圖掌握它,讓你超過95%的其餘已經;-) – BrokenGlass 2012-02-25 02:31:51

13

dlev的回答很好;我只是想我會添加更多的信息。

具體來說,我注意到您正試圖使用​​泛型來實現IEnumerable<T>上的一種協方差。 在C#4及以上版本中,IEnumerable<T>已經是協變的。

你的第二個例子說明了這一點。如果你有

List<List<int>> lists = whatever; 
foreach(int x in lists.Flatten()) { ... } 

然後鍵入推斷會有理由相信List<List<int>>可轉換爲IE<List<int>>List<int>可以轉換爲,因此,由於協方差,IE<List<int>>可轉換爲IE<IE<int>>。這給了類型推理一些繼續;它可以推斷T是int,並且一切都很好。

這在C#3中不起作用。在沒有協變的世界中,生活有點困難,但你可以通過明智地使用Cast<T>擴展方法來獲得。

相關問題