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

C# 翻译LINQ表达式时,C编译器如何选择SelectMany?,c#,select,expression,linq,C#,Select,Expression,Linq,Enumerable.SelectMany有4个重载签名。为了简单起见,我们忽略带有int参数的两个签名。因此,我们为SelectMany提供了两个签名: public static IEnumerable<TResult> SelectMany<TSource, TResult>( this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>>

Enumerable.SelectMany有4个重载签名。为了简单起见,我们忽略带有int参数的两个签名。因此,我们为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表达式中有多个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);
        }
在反射器的帮助下,我可以看到上面的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签名,那么它的存在仅仅是因为它是函数式编程中单子的绑定

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


谢谢。

根据C规范,编译器不会对SelectMany的第一个版本生成重载调用。SelectMany的第一个版本对于将列表列表扁平化为单个扁平列表非常有用

public IEnumerable<string> Example(IEnumerable<IEnumerable<string>> enumerable) {
  return enumerable.SelectMany(x => x);
}
它在查询表达式中没有强等价物

有关更多信息,请参见C语言规范第7.15.2节

为什么我们有两个SelectMany的签名

所以我可以在我的代码中使用第一个

var orders = Customers.SelectMany(c => c.Orders)

当然。这只是为了方便起见。SelectMany的第一个签名同构于一元绑定运算符,用Haskell写成>>=。它包含的一个原因是将LINQ的基础建立在良好的单子结构框架上。该基础使得LINQ多态地适用于各种各样的事物,如可组合状态传播、例外、延续、替代等:所有的单子。看,真的。。。一个空函数返回一些东西,而没有人对它进行评论?