C#3.0泛型类型推断 - 将委托作为函数参数传递

时间:2009-01-02 20:50:54

标签: c# generics delegates c#-3.0 type-inference

我想知道为什么当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
    }   
}

我原本以为我能够将foo传递给bar并让编译器从传递的函数的签名中推断出Action<T>的类型,但这不是工作。但是我可以在没有强制转换的情况下从Action<int>创建foo,那么编译器是否也不能通过类型推断做同样的事情呢?

5 个答案:

答案 0 :(得分:16)

也许这会让它更清晰:

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?
  }

由于一般问题不可解决,他们选择将解决方案存在的具体问题留下未解决的问题。

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

答案 1 :(得分:7)

理由是,如果类型扩大,那么就不应该失败。即,如果方法foo(string)被添加到类型中,它应该对现有代码无关紧要 - 只要现有方法的内容不会改变。

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

答案 2 :(得分:5)

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

我知道MS正在寻求改进类型推断的规范,因此它可能会变得更加清晰。我也知道,无论阅读的难度如何,对方法组和类型推断都有限制 - 当方法组实际上只是一种方法时,这种限制可能是特殊的。无可否认。

Eric Lippert在return type inference not working with method groups上有一个博客条目,与类似 - 但这里我们对返回类型不感兴趣,只对参数类型感兴趣。 other posts in his type inference series可能会有所帮助。

答案 3 :(得分:5)

请记住作业

Action<int> f = foo;

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

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

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

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

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

bar<int>(foo);

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

答案 4 :(得分: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)的类型参数'T'(f As System.Action(Of T))'。