C#3.0genericstypes推断 – 传递一个委托作为函数参数

我想知道为什么C#3.0编译器无法推断方法的types,当它作为parameter passing给generics函数时,它可以隐式地创build一个委托为同一个方法。

这里是一个例子:

class Test { static void foo(int x) { } static void bar<T>(Action<T> f) { } static void test() { Action<int> f = foo; // I can do this bar(f); // and then do this bar(foo); // but this does not work } } 

我会想,我将能够通过foobar并让编译器从被传递的函数的签名中推断出Action<T>的types,但是这不起作用。 不过,我可以从foo创build一个Action<int>而不需要转换,所以编译器不能通过types推断来做同样的事情吗?

也许这会更清楚:

 public class SomeClass { static void foo(int x) { } static void foo(string s) { } static void bar<T>(Action<T> f){} static void barz(Action<int> f) { } static void test() { Action<int> f = foo; bar(f); barz(foo); bar(foo); //these help the compiler to know which types to use bar<int>(foo); bar( (int i) => foo(i)); } } 

foo不是一个动作 – foo是一个方法组。

  • 在赋值语句中,由于指定了inttypes,因此编译器可以清楚地分辨出你在讨论哪个foo。
  • 在barz(foo)语句中,由于指定了inttypes,因此编译器可以知道你在讨论哪个foo。
  • 在bar(foo)语句中,它可以是任何具有单个参数的foo – 所以编译器放弃。

编辑:我已经添加了两种(更多)的方式来帮助编译器找出types(即 – 如何跳过推理步骤)。

从我读到的JSKetet的回答中的文章中,不推断这种types的决定似乎基于相互推断的情况,例如

  static void foo<T>(T x) { } static void bar<T>(Action<T> f) { } static void test() { bar(foo); //wut's T? } 

由于一般问题是无法解决的,他们select留下解决scheme存在的具体问题。

作为这个决定的结果,你将不会为一个方法增加一个重载,并且从用于单个成员方法组的所有调用者中获得大量的types混淆。 我想这是一件好事。

原因是,如果这种types扩展,就不可能失败。 即,如果将某个方法foo(string)添加到该types中,则对现有代码无关紧要 – 只要现有方法的内容不变。

因此,即使只有一个方法foo,对foo(称为方法组)的引用也不能转换为非特定于types的委托,如Action<T>而只能转换为特定于types的委托如Action<int>

这有点奇怪,是的。 types推理的C#3.0规范很难阅读,并且存在错误,但看起来应该可行。 在第一阶段(第7.4.2.1节),我认为有一个错误 – 它不应该提到第一个项目符号中的方法组(因为它们没有被明确的参数types推断(7.4.2.7)所涵盖),这意味着它应该使用输出types推理(7.4.2.6)。 看起来应该可以工作 – 但显然它不是:(

我知道MS正在寻求改进types推断的规范,所以它可能会变得更清晰。 我也知道,不pipe阅读有多困难,方法组和types推断都是有限制的 – 当方法组实际上只是一个单一的方法时,这种限制可能是特殊的。

Eric Lippert有一个关于返回types推断的博客条目,它不是与类似于这种情况的方法组一起工作的 – 但是在这里我们对返回types不感兴趣,只对参数types感兴趣。 他的types推断系列中的其他post可能有帮助。

请记住,分配

 Action<int> f = foo; 

已经有很多的语法糖。 编译器实际上为这个语句生成代码:

 Action<int> f = new Action<int>(foo); 

相应的方法调用没有问题编译:

 bar(new Action<int>(foo)); 

Fwiw,帮助编译器推断types参数:

 bar<int>(foo); 

所以归结到这个问题,为什么糖在赋值语句中,而不是在方法调用? 我不得不猜测,这是因为糖在作业中是毫不含糊的,只有一个可能的替代。 但是在方法调用的情况下,编译器编写者已经不得不处理重载问题。 其规则相当精细。 他们可能只是没有得到解决。

只是为了完整性,这不是特定于C#:同样的VB.NET代码也会失败:

 Imports System Module Test Sub foo(ByVal x As integer) End Sub Sub bar(Of T)(ByVal f As Action(Of T)) End Sub Sub Main() Dim f As Action(Of integer) = AddressOf foo ' I can do this bar(f) ' and then do this bar(AddressOf foo) ' but this does not work End Sub End Module 

错误BC32050:无法推断“Public Sub bar(Of T)(f As System.Action(Of T))”types参数“T”。