【问题标题】:Overload resolution issue for generic method with constraints具有约束的泛型方法的重载解决问题
【发布时间】:2014-09-25 11:28:05
【问题描述】:

代码示例:

interface IFoo { }
class FooImpl : IFoo { }

static void Bar<T>(IEnumerable<T> value)
    where T : IFoo
{
}

static void Bar<T>(T source)
    where T : IFoo
{
}

谁能解释一下,为什么调用这个方法:

var value = new FooImpl[0];
Bar(value);

Bar&lt;T&gt;(T source) 为目标(因此无法编译)?

在解析重载时,编译器是否会考虑类型参数约束?

UPD

为了避免与数组混淆。 IEnumerable&lt;T&gt; 的任何实现都会发生这种情况,例如:

var value = new List<FooImpl>();

UPD 2

@ken2k 提到了协方差。 但是让我们忘记FooImpl。这个:

var value = new List<IFoo>();
Bar(value);

产生同样的错误。
我敢肯定,List&lt;IFoo&gt;IEnumerable&lt;IFoo&gt; 之间存在隐式转换,因为我可以轻松编写如下内容:

static void SomeMethod(IEnumerable<IFoo> sequence) {}

并将value 传递给它:

SomeMethod(value);

【问题讨论】:

  • 刚刚检查,Bar(value) 被接受。
  • @VictorMukherjee:事实上,我想了解,是什么阻止了编译器在没有任何额外提示的情况下定位Bar&lt;T&gt;(IEnumerable&lt;T&gt; value)。乍一看,没有什么可以阻止它。

标签: c# generics overload-resolution


【解决方案1】:

在解析重载时,编译器是否会考虑类型参数约束?

不,因为通用约束不是函数签名的一部分。您可以通过添加除通用约束外相同的Bar 重载来验证这一点:

interface IBar { }

static void Bar<T>(IEnumerable<T> value)
    where T : IFoo
{
}

static void Bar<T>(T source)
    where T : IBar
{
    // fails to compile : Type ____ already defines a member called 'Bar' with the same parameter types
}

您的代码无法编译的原因是编译器根据方法签名选择“最佳”匹配,然后尝试应用通用约束。

的一个可能原因是因为这个调用会模棱两可:

{假设 List&lt;T&gt; 有一个 Add&lt;T&gt;(IEnumerable&lt;T&gt; source) 方法}

List<object> junk = new List<object>();
junk.Add(1);   // OK
junk.Add("xyzzy") // OK
junk.Add(new [] {1, 2, 3, 4});  //ambiguous - do you intend to add the _array_ or the _contents_ of the array?

明显的解决方法是为接受集合的 Bar 方法使用不同的名称(就像在 BCL 中使用 AddAddRange 所做的那样。

【讨论】:

  • 另一个解决方法是使用Bar&lt;IFoo&gt;(value); 指定T 参数是什么
  • “泛型约束不是函数签名的一部分” - 是的,不幸的是……只是输入了类似的示例。
  • 不,因为通用约束不是函数签名的一部分。 -- 从 c# 7.3 开始,这不再适用。添加了一个答案below 表明这一点。
【解决方案2】:

编辑:好的,在传递列表时选择 Bar&lt;T&gt;(T source) 而不是 Bar&lt;T&gt;(IEnumerable&lt;T&gt; source) 的原因是因为 C# 语言参考的“7.5.3.2 Better function member”部分。它的意思是,当必须发生重载决议时,参数类型与适用函数成员的参数类型匹配(第 7.5.3.1 节),并通过以下规则集选择更好的函数成员:

• 对于每个参数,从 EX 到 QX 的隐式转换并不优于从 EX 到 PX 的隐式转换,并且

• 对于至少一个参数,从 EX 到 PX 的转换优于从 EX 到 QX 的转换。

(PX为第一种方法的参数类型,QX为第二种方法)

此规则在“在扩展和类型参数替换之后”应用。由于类型参数替换会将 Bar(T source) 交换为 Bar>(IList source),因此此方法参数将比需要转换的 Bar(IEnumerable source) 更好地匹配。

我找不到语言参考的在线版本,但您可以阅读它here


编辑:最初误解了这个问题,正在努力在 c# 语言规范中找到正确的答案。基本上 IIRC 方法是通过考虑最合适的类型来选择的,如果你没有将参数转换为 IEnumerable&lt;&gt;,那么 Bar&lt;T&gt;(T source) 将完全匹配参数类型,就像在这个示例中一样:

public interface ITest { }
public class Test : ITest { }

private static void Main(string[] args)
{
    test(new Test() ); // outputs "anything" because Test is matched to any type T before ITest
    Console.ReadLine();
}


public static void test<T>(T anything)
{
    Console.WriteLine("anything");
}

public static void test(ITest it)
{
    Console.WriteLine("it");
}

找到时会链接到它


因为数组和可枚举之间的强制转换必须是显式的:这样可以编译

var value = new FooImpl[0].AsEnumerable();
Bar(value);

这也是如此:

var value = new FooImpl[0] as IEnumerable<IFoo>;
Bar(value);

来自the doc

从 .NET Framework 2.0 开始,Array 类实现了 System.Collections.Generic.IList, System.Collections.Generic.ICollection 和 System.Collections.Generic.IEnumerable 通用接口。这 在运行时将实现提供给数组,因此, 通用接口没有出现在声明语法中 数组类。

因此您的编译器不知道该数组与 Bar 的签名匹配,因此您必须显式转换它

【讨论】:

  • 答案有问题,为什么投反对票?
  • 如果传递 List 也会出现同样的问题。不过,我不是反对者
  • @samy:这是一个非常值得怀疑的假设。首先,正如 Victor 所提到的,IEnumerable&lt;T&gt; 的任何其他实现都会发生同样的事情。第二,我不知道,我的问题与您提供的链接有何关联。
  • Dennis,该链接解释了为什么数组在 Bar 中不会被接受为 IEnumerable,但 @VictorMukherjee 的评论明确了问题
【解决方案3】:

这是一个covariance 问题。 List&lt;T&gt; 不是协变的,所以List&lt;FooImpl&gt;List&lt;IFoo&gt; 之间没有隐式转换。

另一方面,从 C# 4 开始,IEnumerable&lt;T&gt; 现在支持协方差,所以这可行:

var value = Enumerable.Empty<FooImpl>();
Bar(value);

var value = new List<FooImpl>().AsEnumerable();
Bar(value);

var value = new List<FooImpl>();
Bar((IEnumerable<IFoo>)value);

【讨论】:

  • 有趣的一点,但是如果我们忘记FooImpl 怎么办?我会尽快更新问题。
【解决方案4】:

c# 7.3 开始,通用约束现在被视为方法签名的一部分,用于重载解析。来自What's new in C# 7.0 through C# 7.3: Improved overload candidates

  1. 当方法组包含一些类型参数不满足其约束的泛型方法时,这些成员将从候选集中移除。

因此在c# 7.3 / .Net Core 2.x及以后,问题中显示的代码编译并运行成功Bar&lt;T&gt;(IEnumerable&lt;T&gt; value)被按需调用。

  • 演示 .Net 5 fiddle here 现在编译成功

  • 演示 .NET Framework 4.7.3 fiddle here 仍然无法编译并出现错误:

    The type 'FooImpl[]' cannot be used as type parameter 'T' in the generic type or method 'TestClass.Bar&lt;T&gt;(T)'. There is no implicit reference conversion from 'FooImpl[]' to 'IFoo'.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-13
    • 1970-01-01
    • 1970-01-01
    • 2015-05-02
    相关资源
    最近更新 更多