【问题标题】:How does compiler infer the delegate type from LAMBDA expression?编译器如何从 LAMBDA 表达式推断委托类型?
【发布时间】:2016-07-02 18:30:53
【问题描述】:

示例代码

int id = 123;

ThreadPool.QueueUserWorkItem(state => ThreadEntryPoint((int)state), id);

public void ThreadEntryPoint(int uniqueId)
{
   Console.WriteLine("uniqueId=" + uniqueId);
}

问题

根据提供的 LAMBDA 表达式,编译器如何知道它需要创建 QueueUserWorkItem(WaitCallback, Object) 的实例?

更具体地说:我知道它是在推断委托类型。我不明白的是(从高层次上)选择正确的委托类型进行实例化的决策树是什么?

参考文献

【问题讨论】:

  • 实际上编译器不会创建任何实例,它不会创建QueueUserWorkItem 的实例,因为那是一种方法。但是 QueueUserWorkItem 方法需要一个 WaitCallback 委托,并且委托本身被指定为没有返回类型 (void) 并采用一个参数 (object),例如 public delegate void WaitCallback(Object state);。您编写的 lambda 语句代表一个匿名方法。所以这个匿名方法将被“封装”在WaitCallback 委托中(添加到调用列表中)。

标签: c# .net reflection lambda


【解决方案1】:

编译器如何从 LAMBDA 表达式推断委托类型?

基本上不会。

编译器从名为QueueUserWorkItem 的方法组中的可用重载推断委托类型。只有两个重载,只有一个有两个参数,而且两个重载都使用委托类型WaitCallback

因此,委托类型必须WaitCallback。确定这一点后,编译器可以将 lambda 表达式编译为匿名方法,并将其实例化为 QueueUserWorkItem() 方法的参数,调用必要的委托对象来调用该匿名方法。

在更复杂的场景中,编译器必须执行一些分析来确定重载的“最佳”匹配,并且该分析可能涉及 lambda 表达式,以至于它可以消除基于 lambda 表达式的重载可能性.

但编译器绝不会从 lambda 表达式开始并直接转到委托类型。对于要转换为委托实例的 lambda 表达式,lambda 表达式需要有一些其他上下文来确定所需的委托类型,例如分配给类型化变量、显式转换或(如在本例中)方法重载,其中使用 lambda 表达式的参数具有与 lambda 表达式兼容的特定委托类型。

请注意,泛型方法仍然存在类型推断,其中 lambda 用于推断类型参数。 Eric Lippert's comment 解释得很清楚:

在某些情况下,编译器必须从 lambda 推断构造的委托类型。例如,如果我们有 M<A, R>(Func<A, R> f)M((string x) => x.Length),那么编译器将首先从 lambda 参数推断 Func<string, something>,然后从 lambda 的主体推断 Func<string, int>

我的意思是编译器不会从头开始推断Func<T, TResult>。正如 Eric 所指出的,编译器确实使用 lambda 来推断类型参数,但编译器仍然需要已知开放泛型类型的上下文。

更多详情,请阅读the C# specification。它将详细说明如何执行重载解析以及管理重载的规则以及将 lambda 表达式与类型匹配。

补充阅读:
.Net lambda expression— where did this parameter come from?
Why can't an anonymous method be assigned to var?
Why can't the compiler tell the better conversion target in this overload resolution case? (covariance)
Convert this delegate to an anonymous method or lambda
并非巧合的是,前三个包括对主题和相关问题的精彩讨论,由 Eric Lippert 撰写,他曾在 Visual Studio C# 编译器和语言设计团队工作。

【讨论】:

  • 我总是感兴趣的是编译器说“我无法推断出更好的匹配,所以我必须失败”的“停止点”,这在不存在隐式转换但存在显式转换的情况下很常见.
  • 那里有两个不同的问题。首先是重载决议,其次是泛型方法的类型推断。但是,是的,在某些情况下,上下文没有充分约束可用的方法重载,编译器无法做出决定。 C# 规范详细说明了重载解析和类型推断规则,如果遵循规范中的规则不会产生唯一的结果,则会导致错误。
  • @Peter:感谢您提供有关其工作原理的一些见解!
  • 在某些情况下编译器必须从 lambda 推断 constructed 委托类型。例如,如果我们有M<A, R>(Func<A, R> f)M((string x) => x.Length),那么编译器将首先从lambda 参数推断Func<string, something>,然后从lambda 的主体推断Func<string, int>。但在任何情况下,编译器都不会从 lambda 中推断出 Func 类型,这就是我认为您在这个答案中得到的结果。
  • @EricLippert:“这就是我认为你在这个答案中得到的结果” - 是的,这是正确的。非常感谢您的额外澄清。我会将其添加到答案中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-15
  • 1970-01-01
相关资源
最近更新 更多