2012-02-25 2 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; 
} 

Edit (2): Этот вопрос я считаю, ответил, но он поднял еще один вопрос, касающийся разрешение перегрузки и ограничения типов. Этот вопрос, который я поставил здесь: Why aren't type constraints part of the method signature?

+1

Ваше редактирование не работает, потому что у вас слишком много окружающих перечислимых. 'foo.Flatten , int>();' должен работать. – dlev

ответ

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

+2

@Daryl Общие ограничения не считаются частью сигнатуры метода; для * пути больше *, см. эту ссылку: http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx – dlev

+1

@Daryl : Не надо - здесь определенная кривая обучения, это далеко не самый простой аспект C# для понимания.Просто попытка овладеть им ставит вас впереди уже 95% остальных ;-) – BrokenGlass

13

Ответ dlev - это хорошо; Я просто подумал, что добавлю немного больше информации.

В частности, я отмечаю, что вы пытаетесь использовать generics для реализации своего рода ковариации на 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>.

Смежные вопросы