Свести 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>
метод расширения.