为什么C#不能从这个看似简单明显的案例中推断出类型

sco*_*obi 67 c#

鉴于此代码:

class C
{
    C()
    {
        Test<string>(A); // fine
        Test((string a) => {}); // fine
        Test((Action<string>)A); // fine

        Test(A); // type arguments cannot be inferred from usage!
    }

    static void Test<T>(Action<T> a) { }

    void A(string _) { }
}
Run Code Online (Sandbox Code Playgroud)

编译器抱怨Test(A)无法弄清楚Tstring.

这对我来说似乎是一个非常简单的案例,我发誓我在其他通用实用程序和扩展函数中依赖于更复杂的推理.我在这里错过了什么?

更新1:这是在C#4.0编译器中.我在VS2010中发现了这个问题,上面的示例来自我在LINQPad 4中制作的最简单的repro.

更新2:在可用的列表中添加了更多示例.

Jef*_*dge 35

Test(A);
Run Code Online (Sandbox Code Playgroud)

这失败是因为唯一适用的方法(Test<T>(Action<T>))需要类型推断,并且类型推断算法要求每个参数都是某种类型或者是匿名函数.(这个事实是从类型推断算法(第7.5.2节)的规范推断出来的)方法组A不是任何类型的(即使它可以转换为适当的委托类型),并且它不是匿名函数.

Test<string>(A);
Run Code Online (Sandbox Code Playgroud)

这成功了,区别在于类型推断不是绑定Test所必需的,方法组A可以转换为所需的委托参数类型void Action<string>(string).

Test((string a) => {});
Run Code Online (Sandbox Code Playgroud)

这成功了,不同之处在于类型推断算法在第一阶段(第7.5.2.1节)中提供了匿名函数.参数和返回类型匿名函数是已知的,所以显式参数类型推断可以被做,和一个correspondense在匿名函数(各类型之间从而制成void ?(string))和委托类型的类型参数Test方法的参数(void Action<T>(T)).没有为与匿名函数的此算法对应的方法组指定算法.

Test((Action<string>)A);
Run Code Online (Sandbox Code Playgroud)

这成功了,不同之处在于将无类型方法组参数A强制转换为类型,从而允许类型推断Test通常使用特定类型的表达式作为方法的唯一参数.

理论上我没理由为什么不能在方法组上尝试重载解析A.然后 - 如果找到单个最佳绑定 - 方法组可以被赋予与匿名函数相同的处理.在这样的情况下尤其如此,其中方法组恰好包含一个候选项并且它没有类型参数.但它在C#4中不起作用的原因似乎是这个功能没有设计和实现.鉴于此功能的复杂性,应用程序的狭隘性以及三种简单解决方案的存在,我不会为此屏住呼吸!

  • 说"方法组只包含一种方法,所以让我们暂时说过,即使我们不知道参数,重载解析成功"实质上是在添加一种新的,奇怪的重载解析算法,该算法仅在包含方法组的极少数情况下成功一种方法.我们真的不想去那里; 你真的想要一种情况,即添加第二个重载会严重改变类型推断的工作方式吗? (8认同)
  • 最后请注意,在C#4中,如果您尝试推断的是*return类型*并且所有*参数类型*已经成功推断,那么重载决策将继续进行. (5认同)
  • 无法尝试重载分辨率,因为重载决策确定*方法*给定*方法组*和*参数*.但这是我们试图解决的论证类型!这是一个鸡蛋和鸡蛋问题,我们不试图解决. (4认同)
  • 感谢您对所有案例进行彻底解释.这为我解决了这个问题! (2认同)

Meh*_*dad 8

我认为这是因为这是一个两步推理:

  • 它必须推断您要将A转换为通用委托

  • 它必须推断委托参数的类型应该是什么

我不确定这是否是原因,但我的预感是,对于编译器来说,两步推理并不一定容易.


编辑:

只是预感,但有些东西告诉我第一步是问题.编译器必须计算转换为具有不同数量的泛型参数的委托,因此它无法推断参数的类型.

  • 也许Eric Lippert会停下来清除这一点.:) (5认同)

Ale*_*exD 5

这看起来像是一个恶性循环.

Testmethod期望从泛型类型构造的委托类型的参数Action<T>.你传入一个方法组:Test(A).这意味着编译器必须将您的参数转换为委托类型(方法组转换).

但是哪种委托类型?要知道我们需要知道的委托类型T.我们没有明确指定它,因此编译器必须推断它以找出委托类型.

要推断方法的类型参数,我们需要知道方法参数的类型,在本例中是委托类型.编译器不知道参数类型因此失败.

在所有其他情况下,任何一种论证都是明显的:

// delegate is created out of anonymous method,
// no method group conversion needed - compiler knows it's Action<string>
Test((string a) => {});

// type of argument is set explicitly
Test((Action<string>)A); 
Run Code Online (Sandbox Code Playgroud)

或显式指定type参数:

Test<string>(A); // compiler knows what type of delegate to convert A to
Run Code Online (Sandbox Code Playgroud)

PS 更多关于类型推断

  • 具体来说,匿名方法和方法组都没有自己的类型,但是匿名方法会导致类型推断做一些额外的工作来利用方法的参数类型(第7.5.2.1节,7.5.2.7),而方法组(即使只有一个成员的方法组)也没有. (2认同)