当翻译LINQ表达式时,C#编译器如何选择SelectMany?

9

Enumerable.SelectMany有4个重载签名。为了简单起见,我们忽略了带有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
)

我的问题是:在将LINQ表达式转换为扩展方法调用时,C#编译器如何选择SelectMany?
基本上,如果LINQ表达式中有多个< strong> from ,就会出现SelectMany。但是,似乎C#编译器只选择第二个签名。第一个签名从未使用过。
        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;

It is translated into:

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

那么,我找不到将LINQ表达式翻译为第一个SelectMany签名的情况。有这样的情况吗?

如果不使用第一个SelectMany签名,则它存在仅因为它是函数式编程中单子的绑定吗?

也许问题可以是:为什么我们有两个SelectMany签名?

谢谢。

2个回答

5
根据C#规范,编译器不会对SelectMany的第一版本生成重载调用。第一版本的SelectMany适用于将一个List中的多个List展平为一个单独的平面列表。
public IEnumerable<string> Example(IEnumerable<IEnumerable<string>> enumerable) {
  return enumerable.SelectMany(x => x);
}

这在查询表达式中没有一个强大的等价物。

更多信息请参见C#语言规范的第7.15.2节。


1
真的吗...一个返回值的空函数,竟然没人评论吗? - Marcel Popescu

5
为什么SelectMany有两个签名?
这样我就可以在我的代码中使用第一个签名。
var orders = Customers.SelectMany(c => c.Orders)

当然。那只是为了方便而已。 - Alex Yakunin
3
SelectMany 的第一个签名与 Haskell 中的单子“绑定”运算符 >>= 是同构的。它被包括在内的一个原因是要将 LINQ 建立在单子的稳固框架上,这使得 LINQ 能够多态地适用于各种事物,如可组合状态传播、异常、延续、替代等,即所有的单子。请参阅 http://en.wikipedia.org/wiki/Monad_(functional_programming)。 - Reb.Cabin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接