Свести IEnumerable <IEnumerable <>>; понимание дженериков - PullRequest
34 голосов
/ 25 февраля 2012

Я написал этот метод расширения (который компилируется):

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 ]

76 голосов
/ 25 февраля 2012

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

16 голосов
/ 25 февраля 2012

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

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

...