为什么C#编译器不认为这种泛型类型推断是不明确的?

Tri*_*nko 5 c# generics type-inference generic-type-argument

鉴于以下课程:

public static class EnumHelper
{
    //Overload 1
    public static List<string> GetStrings<TEnum>(TEnum value)
    {
        return EnumHelper<TEnum>.GetStrings(value);
    }

    //Overload 2
    public static List<string> GetStrings<TEnum>(IEnumerable<TEnum> value)
    {
        return EnumHelper<TEnum>.GetStrings(value);
    }
}
Run Code Online (Sandbox Code Playgroud)

选择两种通用方法之一应用哪些规则?例如,在以下代码中:

List<MyEnum> list;
EnumHelper.GetStrings(list);
Run Code Online (Sandbox Code Playgroud)

它最终调用EnumHelper.GetStrings<List<MyEnum>>(List<MyEnum>)(即Overload 1),即使它看起来同样有效EnumHelper.GetStrings<MyEnum>(IEnumerable<MyEnum>)(即Overload 2).

例如,如果我完全删除了重载1,那么调用仍然编译正常,而是选择标记为重载2的方法.这似乎使通用类型推断有点危险,因为它调用的方法直观地看起来像是更糟糕的匹配.我将List/Enumerable作为类型传递,这似乎非常具体,看起来它应该与具有类似参数的方法匹配(IEnumerable<TEnum>),但是它选择具有更通用的泛型参数的方法(TEnum value).

Jon*_*eet 7

选择两种通用方法之一应用哪些规则?

规范中的规则 - 遗憾的是非常复杂.在ECMA C#5标准中,相关位从第12.6.4.3节开始("更好的功能成员").

但是,在这种情况下,它相对简单.这两种方法都适用,每种方法都会单独进行类型推断:

  • 对于方法1,TEnum推断为List<MyEnum>
  • 对于方法2,TEnum推断为MyEnum

接下来,编译器开始检查从参数到参数的转换,以查看一个转换是否比另一个转换"更好".这将进入第12.6.4.4节("从表达式转换得更好").

此时我们正在考虑这些转换:

  • 重载1:List<MyEnum>List<MyEnum>(TEnum推断为List<MyEnum>)
  • 重载2:List<MyEnum>IEnumerable<MyEnum>(TEnum推断为MyEnum)

幸运的是,第一条规则可以帮助我们:

给定从表达式E转换为类型T1的隐式转换C1,以及从表达式E转换为类型T2的隐式转换C2,如果以下至少一个成立,则C1是比C2更好的转换:

  • E具有类型S,并且存在从S到T1而不是从S到T2的标识转换

还有就是从身份转换List<MyEnum>List<MyEnum>,但不是一个身份转换List<MyEnum>IEnumerable<MyEnum>,因此第一次转换是更好的.

没有任何其他转换需要考虑,因此重载1被视为更好的功能成员.

关于"更一般"与"更具体"参数的争论如果早期阶段在抢七中结束则有效,但事实并非如此:参数参数的"更好转换"在"更具体的参数"之前被考虑.

通常,重载分辨率都非常复杂.它必须考虑继承,泛型,无类型参数(例如,null文字,默认文字,匿名函数),参数数组,所有可能的转换.几乎每当有新功能添加到C#时,它都会影响重载分辨率:(