Подтвердить что ты не робот

Flatten 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). Этот вопрос, на который я отвечаю, ответил, но он поднял еще один вопрос относительно разрешения перегрузки и ограничений типа. Этот вопрос, который я поставил здесь: Почему не являются ограничения типа частью подписи метода?

4b9b3361

Ответ 1

Во-первых, вам не нужно 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>. Это позволяет механизму вывода типа определять оба типа общих типов, поскольку они оба доступны только на основе аргументов, предоставляемых методу.

Ответ 2

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

В частности, я отмечаю, что вы пытаетесь использовать generics для реализации своего рода ковариации на IEnumerable<T>. В С# 4 и выше IEnumerable<T> уже является ковариантным.

Ваш второй пример иллюстрирует это. Если у вас есть

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

тогда выведите вывод о том, что List<List<int>> конвертируется в IE<List<int>>, List<int> конвертируется в IE<int>, и поэтому из-за ковариации IE<List<int>> можно конвертировать в IE<IE<int>>. Это дает тип вывода что-то продолжать; он может заключить, что T является int, и все хорошо.

Это не работает на С# 3. Жизнь немного сложнее в мире без ковариации, но вы можете обойтись разумным использованием метода расширения Cast<T>.