Как компилятор С# выбирает SelectMany при переводе выражения LINQ?

Существует 4 перегруженных подписи для Enumerable.SelectMany. Для простоты мы игнорируем две подписи с аргументом int. Итак, у нас есть 2 подписи для SelectMany:

public static IEnumerable<TResult> SelectMany<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, IEnumerable<TResult>> selector
)

public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, IEnumerable<TCollection>> collectionSelector,
    Func<TSource, TCollection, TResult> resultSelector
)

Мой вопрос: как компилятор С# выбирает SelectMany при переводе выражения LINQ в вызов метода расширения?

По сути, если в выражении LINQ есть несколько from, будет SelectMany. Но, похоже, компилятор С# выбирает только вторую подпись. Первая подпись никогда не используется.

        IEnumerable<int> en1 = Enumerable.Range(1, 3);
        IEnumerable<double> en2 = new double[] { 1.0, 3.14 };

        IEnumerable<string> en3 =
            from i1 in en1
            from i2 in en2
            select (i1 * i2).ToString();

        foreach (var i in en3)
        {
            Console.WriteLine(i);
        }

С помощью Reflector я вижу, что приведенное выше выражение LINQ переводится в

en1.SelectMany<int, double, string>(delegate (int i1) {
        return en2;
    }, delegate (int i1, double i2) {
        double CS$0$0000 = i1 * i2return CS$0$0000.ToString();
    })

В приведенном выше примере используются 3 типа. Итак, разумно выбрать вторую сигнатуру SelectMany. Однако в приведенном ниже примере задействован только один тип, он по-прежнему выбирает вторую подпись.

        IEnumerable<int> en4 =
            from i1 in en1
            from i2 in Enumerable.Range(0, i1)
            select i2;

Он переведен на:

en1.SelectMany<int, int, int>(delegate (int i1) {
        return Enumerable.Range(0, i1);
    }, delegate (int i1, int i2) {
        return i2;
    })

Итак, я не могу найти случай, когда выражение LINQ транслируется в первую подпись SelectMany. Есть такой случай?

Если первая сигнатура SelectMany не используется, то она существует только потому, что это BIND монады в функциональном программировании?

Наверно вопрос может быть: почему у нас 2 подписи SelectMany?

Спасибо.


person Morgan Cheng    schedule 08.01.2009    source источник


Ответы (2)


Согласно C# Spec, компилятор не будет генерировать вызов перегрузки для первой версии SelectMany. Первая версия SelectMany удобна для сведения списка списков в один плоский список.

public IEnumerable<string> Example(IEnumerable<IEnumerable<string>> enumerable) {
  return enumerable.SelectMany(x => x);
}

У него нет строгого эквивалента в выражении запроса.

Дополнительные сведения см. в разделе 7.15.2 спецификации языка C#.

person JaredPar    schedule 08.01.2009
comment
Действительно... функция void возвращает что-то, и никто не комментирует это? - person Marcel Popescu; 20.07.2010

почему у нас 2 подписи SelectMany?

Поэтому я могу использовать первый в своем коде.

var orders = Customers.SelectMany(c => c.Orders)
person Amy B    schedule 09.01.2009
comment
Конечно. Это просто для удобства. - person Alex Yakunin; 18.11.2009
comment
Первая сигнатура SelectMany изоморфна монадическому оператору связывания, написанному ››= на Haskell. Одна из причин, по которой он включен, состоит в том, чтобы укрепить основу LINQ на хорошо зарекомендовавшей себя структуре монад. Эта основа делает LINQ полиморфно применимым ко всем видам вещей, таким как составное распространение состояния, исключения, продолжения, альтернативы и т. д.: ко всем монадам. См. en.wikipedia.org/wiki/Monad_(functional_programming). - person Reb.Cabin; 03.06.2011