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

2 ответа

Решение

Во-первых, вам не нужно Flatten(); that method already exists, and is called 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> аргумент. Это позволяет механизму вывода типов определять оба универсальных типа, поскольку они оба доступны только на основе аргументов, предоставленных методу.

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

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

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

Другие вопросы по тегам