为什么 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 _) { }
}

编译器抱怨Test(A)无法弄清楚T string

这对我来说似乎是一个相当容易的情况,我发誓我在我编写的其他通用实用程序和扩展函数中依赖于更复杂的推理。我在这里错过了什么?

更新 1:这是在 C# 4.0 编译器中。我在VS2010中发现了这个问题,上面的示例来自我在LINQPad 4中创建的最简单情况的重现。

更新 2:在有效列表中添加了更多示例。

Test(A);

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

Test<string>(A);

此操作成功,区别在于绑定 Test 不需要类型推断,并且方法组 A 可转换为所需的委托参数类型void Action<string>(string)

Test((string a) => {});

这成功了,区别在于类型推断算法在第一阶段(§7.5.2.1(中为匿名函数提供了条件。匿名函数的参数和返回类型是已知的,因此可以进行显式的参数类型推断,从而在匿名函数中的类型(void ?(string)(和Test方法参数的委托类型中的类型参数(void Action<T>(T)(之间建立对应关系。对于匿名函数,未为对应于此算法的方法组指定算法。

Test((Action<string>)A);

这是成功的,区别在于将非类型化方法组参数A强制转换为类型,从而允许Test的类型推断以特定类型的表达式作为方法的唯一参数正常进行。

理论上我想不出为什么无法在方法组A上尝试过载解决。然后,如果找到单个最佳绑定,则可以为方法组提供与匿名函数相同的处理方式。在方法组只包含一个候选项并且没有类型参数的情况下尤其如此。但它在 C#4 中不起作用的原因似乎是这个功能不是设计和实现的。鉴于此功能的复杂性,其应用的不稳定性以及三种简单解决方法的存在,我不会为此屏住呼吸!

我认为这是因为

这是一个两步推理:

  • 它必须推断您要将 A 转换为泛型委托

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

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

<小时 />

编辑:

只是一种预感,但有些事情告诉我第一步是问题。编译器必须弄清楚转换为具有不同数量的泛型参数的委托,因此它无法推断参数的类型。

这对我来说看起来像是一个恶性循环。

Test方法需要一个从泛型类型 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); 

或显式指定类型参数:

Test<string>(A); // compiler knows what type of delegate to convert A to

附言更多关于类型推断的信息

您正在传递方法 A 的名称。 .Net 框架可以将其转换为 Action ,但它是隐式的,它不会对此负责。

但是,方法名称仍然不是显式Action<>对象。因此,它不会将类型推断为Action类型。

我可能是错的,但我想 C# 无法推断类型的真正原因是由于方法重载和出现的歧义。例如,假设我有以下方法:void foo (int)void foo (float)。现在如果我写var f = foo.编译器应该选择哪个foo?同样,使用Test(foo)的示例也会发生同样的问题。

最新更新