24

我想知道为什么当 C# 3.0 编译器可以隐式地为同一方法创建委托时,当它作为参数传递给泛型函数时,它无法推断方法的类型。

这是一个例子:

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>但这不起作用。但是,我可以在不强制转换的情况下创建一个Action<int>from foo,所以编译器不能通过类型推断来做同样的事情是否有正当理由?

4

5 回答 5

18

也许这会让它更清楚:

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 是一个方法组。

  • 在赋值语句中,编译器可以清楚地分辨出你在说哪个 foo,因为指定了 int 类型。
  • 在 barz(foo) 语句中,编译器可以分辨出您正在谈论的是哪个 foo,因为指定了 int 类型。
  • 在 bar(foo) 语句中,它可以是任何带有单个参数的 foo - 所以编译器放弃了。

编辑:我添加了两种(更多)方法来帮助编译器找出类型(即 - 如何跳过推理步骤)。

从我阅读 JSkeet 回答中的文章来看,不推断类型的决定似乎是基于相互推断的场景,例如

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

由于一般问题无法解决,因此他们选择将存在解决方案的特定问题保留为未解决。

由于此决定,您不会为方法添加重载,并且不会从所有用于单个成员方法组的调用者那里获得大量类型混淆。我想这是一件好事。

于 2009-01-02T21:00:10.773 回答
7

理由是,如果类型扩展,则应该没有失败的可能性。即,如果将方法 foo(string) 添加到类型中,则它与现有代码无关——只要现有方法的内容不改变。

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

于 2009-01-02T21:05:37.033 回答
5

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

我知道 MS 正在寻求改进类型推断的规范,所以它可能会变得更清晰一些。我也知道,不管阅读它的难度如何,方法组和类型推断都有限制——当方法组实际上只是一个方法时,这些限制可能是特殊情况,诚然。

Eric Lippert 有一篇关于返回类型推断不与方法组一起工作的博客条目,这与这种情况类似——但这里我们对返回类型不感兴趣,只对参数类型感兴趣。不过,他的类型推断系列中的其他帖子可能会有所帮助。

于 2009-01-02T21:02:55.277 回答
5

请记住,分配

Action<int> f = foo;

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

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

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

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

Fwiw,帮助编译器推断类型参数也是如此:

bar<int>(foo);

所以归结为一个问题,为什么赋值语句中的糖而不是方法调用中的糖?我不得不猜测这是因为分配中的糖是明确的,只有一种可能的替换。但是在方法调用的情况下,编译器编写者已经不得不处理重载解决问题。其中的规则相当详尽。他们可能只是没有解决它。

于 2009-01-02T22:27:33.903 回答
0

只是为了完整起见,这并不特定于 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))”的类型参数“T”。

于 2011-07-04T14:36:47.910 回答