2012-02-25 8 views
28

Napisałem ten sposób przedłużenie (który kompiluje):Spłaszczyć IEnumerable <IEnumerable <>>; zrozumienia rodzajowych

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; 
} 

Poniższy kod powoduje błąd czasu kompilacji (nie znaleziono odpowiedniej metody), dlaczego?:

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

Gdybym wdrożyć rozszerzenie jak poniżej, mam żadnych kompilacji błąd czasu:

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; 
} 

Edit (2): To pytanie uważam odpowiedział, ale podniósł kolejne pytanie dotyczące rozdzielczość przeciążeniowa i ograniczenia typu. To pytanie mam tutaj: Why aren't type constraints part of the method signature?

+1

Twoja edycja nie działa, ponieważ masz zbyt wiele wyliczanych okolic. 'foo.Flatten , int>();' powinien działać. – dlev

Odpowiedz

65

Po pierwsze, nie potrzebujesz Flatten(); ta metoda już istnieje i nazywa się SelectMany(). Można go używać tak:

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

drugie, Twoja pierwsza próba nie działa, ponieważ typ rodzajowy wnioskowanie działa tylko na podstawie argumentów metody, a nie ogólne ograniczenia związane z metodą. Ponieważ nie ma argumentu, który bezpośrednio wykorzystuje ogólny parametr J, mechanizm wnioskowania o typie nie może odgadnąć, co powinno być J, a tym samym nie uważa, że ​​twoja metoda jest kandydatem.

Podkreśla, jak radzi sobie z tym SelectMany(): wymaga dodatkowego argumentu Func<TSource, TResult>. Dzięki temu mechanizm wnioskowania typu może określać oba typy ogólne, ponieważ oba są dostępne wyłącznie na podstawie argumentów dostarczonych do metody.

+1

@Daryl: Ponieważ powinno być 'Spłaszczone , int> (foo)' – BrokenGlass

+2

@Daryl Ograniczenia ogólne nie są uważane za część sygnatury metody; * więcej *, patrz: http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx – dlev

+1

@Daryl : Nie - zdecydowanie istnieje tu krzywa uczenia się, nie jest to zdecydowanie najłatwiejszy aspekt C# do zrozumienia.Po prostu próbując opanować to już wyprzedza 95% reszty ;-) – BrokenGlass

13

Odpowiedź dlev'a jest w porządku; Po prostu pomyślałem, że dodam trochę więcej informacji.

W szczególności, zauważam, że próbujesz użyć generycznych do implementacji pewnego rodzaju kowariancji na IEnumerable<T>. W języku C# 4 i nowszym, IEnumerable<T> jest już współzmiennym.

Twój drugi przykład ilustruje to. Jeśli masz

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

następnie wpisać wnioskowanie będą rozumieć, że List<List<int>> jest zamienny do IE<List<int>>, List<int> jest zamienny do , a więc z powodu kowariancji, IE<List<int>> jest zamienny do IE<IE<int>>. To daje wnioskowanie o typie, które należy kontynuować; może wywnioskować, że T jest int, i wszystko jest dobre.

To nie działa w C# 3. Życie jest nieco trudniejsze w świecie bez kowariancji, ale można uzyskać rozsądne wykorzystanie metody rozszerzania Cast<T>.