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

时间:2022-02-17 17:04:19

I am wondering why the C# 3.0 compiler is unable to infer the type of a method when it is passed as a parameter to a generic function when it can implicitly create a delegate for the same method.

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

Here is an example:

这是一个例子:

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

I would have thought that I would be able to pass foo to bar and have the compiler infer the type of Action<T> from the signature of the function being passed but this does not work. However I can create an Action<int> from foo without casting so is there a legitimate reason that the compiler could not also do the same thing via type inference?

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

5 个解决方案

#1


Maybe this will make it clearer:

也许这会让它更清晰:

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 is not an action - foo is a method group.

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

  • In the assignment statement, the compiler can tell clearly which foo you're talking about, since the int type is specified.
  • 在赋值语句中,编译器可以清楚地告诉您正在讨论哪个foo,因为指定了int类型。

  • In the barz(foo) statement, the compiler can tell which foo you're talking about, since the int type is specified.
  • 在barz(foo)语句中,编译器可以告诉您正在讨论哪个foo,因为指定了int类型。

  • In the bar(foo) statement, it could be any foo with a single parameter - so the compiler gives up.
  • 在bar(foo)语句中,它可以是带有单个参数的任何foo - 因此编译器放弃了。

Edit: I've added two (more) ways to help the compiler figure out the type (ie - how to skip the inference steps).

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

From my reading of the article in JSkeet's answer, the decision to not infer the type seems to be based on a mutual infering scenario, such as

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

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

Since the general problem was unsolve-able, they choose to left specific problems where a solution exists as unsolved.

由于一般问题是不可解决的,因此他们选择在解决方案存在的情况下留下未解决的特定问题。

As a consequence of this decision, you won't be adding a overload for a method and getting a whole lot of type confusion from all the callers that are used to a single member method group. I guess that's a good thing.

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

#2


The reasoning is that if the type ever expands there should be no possibility of failure. i.e., if a method foo(string) is added to the type, it should never matter to existing code - as long as the contents of existing methods don't change.

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

For that reason, even when there is only one method foo, a reference to foo (known as a method group) cannot be cast to a non-type-specific delegate, such as Action<T> but only to a type-specific delegate such as Action<int>.

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

#3


That is slightly odd, yes. The C# 3.0 spec for type inference is hard to read and has mistakes in it, but it looks like it should work. In the first phase (section 7.4.2.1) I believe there's a mistake - it shouldn't mention method groups in the first bullet (as they're not covered by explicit parameter type inference (7.4.2.7) - which means it should use output type inference (7.4.2.6). That looks like it should work - but obviously it doesn't :(

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

I know that MS is looking to improve the spec for type inference, so it might become a little clearer. I also know that regardless of the difficulty of reading it, there are restrictions on method groups and type inference - restrictions which could be special-cased when the method group is only actually a single method, admittedly.

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

Eric Lippert has a blog entry on return type inference not working with method groups which is similar to this case - but here we're not interested in the return type, only on the parameter type. It's possible that other posts in his type inference series may help though.

Eric Lippert有一个关于返回类型推断的博客条目没有使用方法组,这与这种情况类似 - 但是在这里我们对返回类型不感兴趣,只对参数类型感兴趣。虽然他的类型推断系列中的其他帖子可能会有所帮助。

#4


Keep in mind that the assignment

请记住作业

Action<int> f = foo;

already has lots of syntactic sugar. The compiler actually generates code for this statement:

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

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

The corresponding method call compiles without problem:

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

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

Fwiw, so does helping the compiler to deduce the type argument:

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

bar<int>(foo);

So it boils down to the question, why the sugar in the assignment statement but not in the method call? I'd have to guess that it's because the sugar is unambiguous in the assignment, there is only one possible substitution. But in the case of method calls, the compiler writers already had to deal with the overload resolution problem. The rules of which are quite elaborate. They probably just didn't get around to it.

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

#5


Just for completeness, this is not specific to C#: The same VB.NET code fails similarly:

为了完整起见,这不是特定于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

error BC32050: Type parameter 'T' for 'Public Sub bar(Of T)(f As System.Action(Of T))' cannot be inferred.

错误BC32050:无法推断出'Public Sub bar(Of T)的类型参数'T'(f As System.Action(Of T))'。

#1


Maybe this will make it clearer:

也许这会让它更清晰:

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 is not an action - foo is a method group.

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

  • In the assignment statement, the compiler can tell clearly which foo you're talking about, since the int type is specified.
  • 在赋值语句中,编译器可以清楚地告诉您正在讨论哪个foo,因为指定了int类型。

  • In the barz(foo) statement, the compiler can tell which foo you're talking about, since the int type is specified.
  • 在barz(foo)语句中,编译器可以告诉您正在讨论哪个foo,因为指定了int类型。

  • In the bar(foo) statement, it could be any foo with a single parameter - so the compiler gives up.
  • 在bar(foo)语句中,它可以是带有单个参数的任何foo - 因此编译器放弃了。

Edit: I've added two (more) ways to help the compiler figure out the type (ie - how to skip the inference steps).

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

From my reading of the article in JSkeet's answer, the decision to not infer the type seems to be based on a mutual infering scenario, such as

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

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

Since the general problem was unsolve-able, they choose to left specific problems where a solution exists as unsolved.

由于一般问题是不可解决的,因此他们选择在解决方案存在的情况下留下未解决的特定问题。

As a consequence of this decision, you won't be adding a overload for a method and getting a whole lot of type confusion from all the callers that are used to a single member method group. I guess that's a good thing.

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

#2


The reasoning is that if the type ever expands there should be no possibility of failure. i.e., if a method foo(string) is added to the type, it should never matter to existing code - as long as the contents of existing methods don't change.

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

For that reason, even when there is only one method foo, a reference to foo (known as a method group) cannot be cast to a non-type-specific delegate, such as Action<T> but only to a type-specific delegate such as Action<int>.

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

#3


That is slightly odd, yes. The C# 3.0 spec for type inference is hard to read and has mistakes in it, but it looks like it should work. In the first phase (section 7.4.2.1) I believe there's a mistake - it shouldn't mention method groups in the first bullet (as they're not covered by explicit parameter type inference (7.4.2.7) - which means it should use output type inference (7.4.2.6). That looks like it should work - but obviously it doesn't :(

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

I know that MS is looking to improve the spec for type inference, so it might become a little clearer. I also know that regardless of the difficulty of reading it, there are restrictions on method groups and type inference - restrictions which could be special-cased when the method group is only actually a single method, admittedly.

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

Eric Lippert has a blog entry on return type inference not working with method groups which is similar to this case - but here we're not interested in the return type, only on the parameter type. It's possible that other posts in his type inference series may help though.

Eric Lippert有一个关于返回类型推断的博客条目没有使用方法组,这与这种情况类似 - 但是在这里我们对返回类型不感兴趣,只对参数类型感兴趣。虽然他的类型推断系列中的其他帖子可能会有所帮助。

#4


Keep in mind that the assignment

请记住作业

Action<int> f = foo;

already has lots of syntactic sugar. The compiler actually generates code for this statement:

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

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

The corresponding method call compiles without problem:

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

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

Fwiw, so does helping the compiler to deduce the type argument:

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

bar<int>(foo);

So it boils down to the question, why the sugar in the assignment statement but not in the method call? I'd have to guess that it's because the sugar is unambiguous in the assignment, there is only one possible substitution. But in the case of method calls, the compiler writers already had to deal with the overload resolution problem. The rules of which are quite elaborate. They probably just didn't get around to it.

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

#5


Just for completeness, this is not specific to C#: The same VB.NET code fails similarly:

为了完整起见,这不是特定于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

error BC32050: Type parameter 'T' for 'Public Sub bar(Of T)(f As System.Action(Of T))' cannot be inferred.

错误BC32050:无法推断出'Public Sub bar(Of T)的类型参数'T'(f As System.Action(Of T))'。