【问题标题】:C# 3.0 generic type inference - passing a delegate as a function parameterC# 3.0 泛型类型推断 - 将委托作为函数参数传递
【发布时间】:2010-09-29 07:25:21
【问题描述】:

我想知道为什么当 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&lt;T&gt; 的类型,但这不起作用。但是,我可以从 foo 创建一个 Action&lt;int&gt; 而无需强制转换,那么编译器是否有正当理由不能通过类型推断也做同样的事情?

【问题讨论】:

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


    【解决方案1】:

    也许这样会更清楚:

    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) 语句中,由于指定了 int 类型,编译器可以分辨出您所说的是哪个 foo。
    • 在 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?
      }
    

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

    作为这个决定的结果,您不会为方法添加重载,也不会从习惯于单个成员方法组的所有调用者那里得到大量的类型混淆。我想这是件好事。

    【讨论】:

    • 那么即使只找到一种匹配方法,编译器也会放弃?这也许是他们添加的那些东西之一,以降低未来对代码的更改不自觉地改变现有代码的含义的可能性?
    • 这与我正在寻找的很接近,但在我的示例中,方法组只有一个方法,所以我认为编译器会知道从该方法推断委托类型。在您的示例中,编译器显然很困惑,因为有多个 foo 函数。
    • @AndrewHare:虽然在少数情况下可以从方法组中推断出参数T,但在大多数情况下不能。例如,给定void XX(object s); Action&lt;T&gt; YY&lt;T&gt;(Action&lt;T&gt; a); ...var qq=YY(XX);qq 可以是什么类型?尽管将Action&lt;object&gt; 看作是最自然的,但对于从object 继承的任何类类型TXX 可以分配给Action&lt;T&gt;。仅在模棱两可的情况下允许类型推断有时会有所帮助,但何时允许或不允许的规则可能会令人困惑。
    • 刚刚遇到了一个类似的问题(使用用户定义的类型/接口而不是“操作”),并通过其中一个重复项找到了这个答案......看起来我需要在来自调用站点的重新设计或许多丑陋的强制转换/显式类型参数。感谢您的清晰解释。
    【解决方案2】:

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

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

    【讨论】:

    • 其他变化如何影响这一点?这是某处的规则或指南吗?我可以看到其他更改的一些明显问题,例如 foo(float i) { ... },然后是 foo(10),然后添加 foo(int i)。
    • 这是一个很好的观点——我没有想到这一点。但是,如果我稍后确实添加了一个新的 foo ,那么编译器(恕我直言)应该会在实际出现问题时中断并抱怨歧义。现在,当没有其他 foo 时,它似乎应该可以工作。
    • 就像现在一样,它将现有方法更改为调用 foo(int i),而不是像原来那样将整数文字隐式转换为浮点值并调用 foo(float i)。有很多这样的变化,但我认为他们在阻止它们方面做得更好,也许这就是原因..
    • @lassevk - 我所说的是一种简化,仅针对不匹配的类型,即字符串、int 和 SomeOtherType,而不是会导致隐式转换的类型或更具体的类型导致多态性发挥作用。
    • @configurator - 是的,歧义是一个潜在的选项,但目前不是一个选项。为什么编译器要打破潜在的歧义?
    【解决方案3】:

    这有点奇怪,是的。用于类型推断的 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 可能会有所帮助。

    【讨论】:

    • 在规范中,它提到了关于方法调用的第 7.5.5.1 章,其中说如果方法具有相同数量的泛型参数,并且参数与方法参数的类型匹配,则该方法是兼容的,它没有说明推导匹配的通用参数类型...
    • 在 7.5.5.1 中有这样的内容:“o 如果 F 是泛型且 M 没有类型参数列表,则 F 是候选对象:类型推断 (§7.4.2) 成功,推断类型列表调用的参数 [...]"
    • 嗯,好吧,我想我需要再转过头来围绕这个 C# 规范 :)
    【解决方案4】:

    记住这个任务

    Action<int> f = foo;
    

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

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

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

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

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

    bar<int>(foo);
    

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-18
        • 2019-03-05
        • 2016-10-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多