为啥 C# 无法从非泛型静态方法的签名推断泛型类型参数类型?

Posted

技术标签:

【中文标题】为啥 C# 无法从非泛型静态方法的签名推断泛型类型参数类型?【英文标题】:Why is C# unable to infer the generic type argument type from a non-generic static method's signature?为什么 C# 无法从非泛型静态方法的签名推断泛型类型参数类型? 【发布时间】:2014-01-06 02:26:05 【问题描述】:

我进行了以下推理测试:

static class InferenceTest 
    static void TakeInt(int a)  
    static int GiveInt()  return 0; 
    static int TakeAndGiveInt(int a)  return 0; 

    static void ConsumeAction1<T>(Action<T> a)  
    static void ConsumeFunc1<T>(Func<T> f)  
    static void ConsumeFunc2a<T1, T2>(Func<T1, T2> f)  
    static void ConsumeFunc2b<T>(Func<int, T> f)  
    static void ConsumeFunc2c<T>(Func<T, T> f)  
    static void ConsumeFunc1Func2<T1, T2>(Func<T1> f1, Func<T1, T2> f2)  

    static void Main() 
        ConsumeAction1(TakeInt);        //error
        ConsumeFunc1(GiveInt);          //ok
        ConsumeFunc2a(TakeAndGiveInt);  //error
        ConsumeFunc2b(TakeAndGiveInt);  //ok
        ConsumeFunc2c(TakeAndGiveInt);  //error
        ConsumeFunc1Func2(GiveInt, TakeAndGiveInt); //ok
    

结果似乎表明 C# 编译器无法从非泛型方法组推断委托函数参数的泛型类型参数。

最让我困惑的是,C# 可以从ConsumeFunc1Func2 中的方法返回值推断Func&lt;T1, T2&gt; 的类型参数,但无法推断ConsumeFunc2cFunc&lt;T, T&gt; 的类型。

此问题类似于T of Func<S, T> is inferred from output of lambda expression only when S and T are different? 问题,但我们使用非泛型方法组来代替具有未知参数类型的 lambda。

Why can't C# infer type from this seemingly simple, obvious case 问题回答了“为什么非模棱两可的非泛型方法不足以进行推理?”和“为什么参数类型和推理的返回值类型之间存在差异?”。

问题:

为什么C#编译器可以使用返回值的类型推断Func&lt;T&gt;的类型,但是在Func&lt;T, T&gt;的情况下看不到成功?

为什么C#编译器可以从ConsumeFunc1Func2中的Func&lt;T1&gt;推断Func&lt;T1, T2&gt;T1类型参数,但不能从ConsumeFunc2c中的自身推断Func&lt;T, T&gt;T类型参数,这似乎更容易?

【问题讨论】:

在 ConsumeFunc1Func2 中,编译仍然只是从返回值推断,而不是参数类型。 T1 由 GiveInt 的返回值解析,T2 由 TakeAndGiveInt 的返回值解析。因此,ConsumeFunc1Func2 案例并没有增加额外的谜团。 我会好好阅读 C# 4.0 规范的第 7.5.2 节。它非常易读,描述了类型推断的各个阶段,以及它们与方法组的关系。 ConsumeFunc2b 表明对于Func&lt;?, T&gt;,返回类型T 可以从TakeAndGiveInt 解析。但是当? 也是T 时,就像ConsumeFunc2c 中的Func&lt;T, T&gt; 一样,编译器似乎忘记了参数T 与已经推断出的Ts 相同。与ConsumeFunc1Func2 的成功完全不同。 【参考方案1】:

通常,方法名称不会唯一标识可以分配方法组的唯一类型Action&lt;T&gt;。例如,即使只有一个 Fred 重载并且它需要一个 Cat 参数,该重载不仅可以分配给 Action&lt;Cat&gt;,还可以分配给其他一些类型,例如 Action&lt;Mammal&gt;Action&lt;Animal&gt; ,或Action&lt;Object&gt;。虽然在某些情况下,一种类型替换在各方面都优于任何替代方案,但情况并非总是如此。定义语言以要求指定委托的类型比让编译器尝试“猜测”更简洁,特别是因为让编译器猜测意味着许多不应该破坏更改的事情将是(例如添加方法重载可能会使以前可以工作的类型推断不明确)。

【讨论】:

【参考方案2】:

不检查方法参数。

正如建议的那样,在 ConsumeFunc1Func2 中,编译器仅根据返回值进行推断。 在 ConsumeFunc2c 中,不检查 TakeAndGiveInt 签名以查看其方法参数类型是否实际上与方法返回类型的类型相同原因...不检查方法参数!

【讨论】:

以上是关于为啥 C# 无法从非泛型静态方法的签名推断泛型类型参数类型?的主要内容,如果未能解决你的问题,请参考以下文章

为啥为非泛型方法或构造函数提供显式类型参数会编译?

C# 2.0 新特性(上)

为啥在存在非泛型时选择泛型方法?

为啥接口的泛型方法可以在 Java 中实现为非泛型?

对于泛型+非泛型,是不是/为啥需要从两次继承 WebViewPage?

为啥 TypeScript 中的方法链接会导致泛型类型推断失败?