编译器如何知道 lambda 表达式应该是什么数据类型

How does the compiler know what datatype the lambda expression should be

因此,当您使用 EF Core 并使用大部分 Linq 扩展时,您实际使用的是 System.Linq.Expressions 而不是通常的 Func.

假设您在 DbSet.

上使用 FirstOrDefault
DbContext.Foos.FirstOrDefault(x=> x.Bar == true);

当你在 FirstOrDefaultctrl + lmb 时,它会显示以下重载:

public static TSource FirstOrDefault<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)

但是Func也有一个重载:

public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)

当您想将表达式存储在变量中时,您可以执行以下操作:

Func<Entity, bool> = x => x.Bar == true;

Expression<Func<Entity, bool>> = x => x.Bar == true;

那么在使用这些扩展方法时,编译器如何决定应该使用哪个重载?

我认为 C# 规范中最有用的地方是 Anonymous Function Expressions:

An anonymous function does not have a value or type in and of itself, but is convertible to a compatible delegate or expression tree type

...

In an implicitly typed parameter list, the types of the parameters are inferred from the context in which the anonymous function occurs—specifically, when the anonymous function is converted to a compatible delegate type or expression tree type, that type provides the parameter types.

然后将我们引向 Anonymous Function Conversions:

A lambda expression F is compatible with an expression tree type Expression<D> if F is compatible with the delegate type D. Note that this does not apply to anonymous methods, only lambda expressions.


这些是规范中的干货。但是,阅读 Eric Lippert 的 How do we ensure that type inference terminates to pull bits together 也很有帮助。

继承class接近度比精确的方法参数类型更重要

请注意,Expression<Func<T,bool>> 变体适用于 IQueryable<T>,而 Func<T, bool> 变体适用于 IEnumerable<T>
在寻找匹配方法时,编译器总是会选择最接近对象类型的方法。继承层次如下:

DbSet<T> : IQueryable<T> : IEnumerable<T>

注意:中间可能还有其他的继承关系,但这并不重要。重要的是哪个最接近 DbSet<T>IQueryable<T>DbSet<T> 的关系比 IEnumerable<T> 更近。

因此,编译器会尝试在IQueryable<T>中寻找匹配的方法。它会问两个问题:

  • 这个类型有那个名字的方法吗?
  • 方法参数类型match/map?

IQueryable<T> 有一个 FirstOrDefault 方法,因此满足要点 1);由于 x => x.MyBoolean 可以隐式转换为 Expression<Func<T, bool>>,因此也满足要点 2。

因此,您最终得到在 IQueryable<T> 上定义的 Expression<Func<T,bool>> 变体。

假设 x => x.MyBoolean 可以 而不是 隐式转换为 Expression<Func<T,bool>> 但可以转换为 Func<T,bool>(注意:情况并非如此,但其他 types/values 可能会发生这种情况),那么要点 2 将 得到满足。
此时,由于编译器未在 IQueryable<T> 中找到匹配项,它将继续查找,在 IEnumerable<T> 上绊倒并问自己相同的问题(要点)。两个要点都会得到满足。

因此,在这种情况下,您最终会得到 IEnumerable<T> 上定义的 Func<T,bool> 变体。

更新

Here's a dotnetfiddle example.

请注意,即使我传递了 int 值(基本方法签名使用的值),Derived class 的 double 签名也适合(因为 int 隐式转换为 double) 并且编译器从不查找 Base class.

然而,这在 Derived2 中并非如此。由于 int 不会隐式转换为 string,因此在 Derived2 中找不到匹配项,编译器会在 Base 中进一步查找并使用 int 中的方法Base.

接受的答案是一个合理的解释,但我想我可能会提供更多细节。

So lets say you are using FirstOrDefault on a DbSet. DbContext.Foos.FirstOrDefault(x=> x.Bar == true);

首先,我希望你不要这样写。你要问"is it raining?"你问"is it raining?"还是问"is the statement that it is raining a true statement?"就说FirstOrDefault(x => x.Bar).

接下来,给定这些重载:

public static TSource FirstOrDefault<TSource>(
    this IQueryable<TSource> source, 
    Expression<Func<TSource, bool>> predicate)

public static TSource FirstOrDefault<TSource>(
    this IEnumerable<TSource> source, 
    Func<TSource, bool> predicate)

编译器如何选择最好的重载?

首先我们进行类型推断来确定每个TSource是什么。类型推断算法的细节很复杂;如果您对此有疑问,请提出更集中的问题。

如果类型推断无法确定 TSource 的类型,失败的推断方法将从候选集中丢弃。在您的示例中,TSource 可以确定为 Foo,大概是。

接下来,在剩下的候选人中,我们检查他们论证对形式的适用性。也就是说,我们能否将每个提供的参数转换为其对应的形式参数类型? (当然,提供的参数数量是否正确,等等。)在您的示例中,两种方法都适用。

在剩余的适用候选人中,我们现在进入一轮优度检查。优势检查如何工作?同样,我们逐个论证地进行。在这种情况下,我们有两个问题需要回答:

  • DbContext.Foos 可以转换为 IEnumerable<Foo>IQueryable<Foo>。如果有的话,哪种转换更好?
  • lambda 可以转换为委托或表达式树。如果有的话,哪种转换更好?

第二个问题很容易回答:两者都不是更好。关于更好,我们从这个论点中一无所获。

为了回答第一个问题,我们应用规则转换为特定的比转换为一般的要好。如果可以选择转换为长颈鹿或哺乳动物,则转换为长颈鹿更好。所以现在的问题是 更具体 IQueryable<Foo>IEnumerable<Foo>?

特异性检查的规​​则很简单:如果 X 可以隐式转换为 Y 但 Y 不能隐式转换为 X,则 X 更具体。 Giraffe 可以用在需要 Animal 的地方,但是 Animal 不能用在需要 Giraffe 的地方,所以 Giraffe 更具体。又或者:every giraffe is an animal, 但不是每一种动物都是giraffe,所以giraffe更具体

通过这种衡量,IQueryable<T>IEnumerable<T> 更具体,因为每个可查询项都是可枚举的,但并非每个可枚举项都是可查询的。

所以可查询更具体,因此转换更好。

现在我们提出问题"is there a unique applicable candidate method where compared to every other candidate, at least one conversion was better and no conversion was worse?"有;可查询的候选者具有 属性,它在一个参数中比其他参数更好,并且在所有其他参数中都不差,并且它是具有此 属性.

的唯一方法

因此重载解析选择该方法。

如果您有更多问题,我鼓励您阅读规范。