具有约束的generics方法的重载解决问题

代码示例:

interface IFoo { } class FooImpl : IFoo { } static void Bar(IEnumerable value) where T : IFoo { } static void Bar(T source) where T : IFoo { } 

任何人都可以解释,为什么这个方法调用:

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

目标Bar(T source) (因此,不编译)?

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

UPD

避免与数组混淆。 IEnumerable任何实现都会发生这种情况,例如:

 var value = new List(); 

UPD 2

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

 var value = new List(); Bar(value); 

产生相同的错误。
我敢肯定, ListIEnumerable存在隐式转换,因为我可以轻松地编写如下内容:

 static void SomeMethod(IEnumerable sequence) {} 

并将value传递给它:

 SomeMethod(value); 

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

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

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

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

这样做的一个可能原因是因为这个调用不明确:

{假设List有一个Add(IEnumerable source )方法}

 List junk = new List(); 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方法使用不同的名称(如使用AddAddRange在BCL中完成的AddRange

编辑:好的,当传递List时, Bar(T source)被选中而不是Bar(IEnumerable 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源)交换为Bar>(IList源),因此此方法参数将比需要转换的Bar(IEnumerable源)更好地匹配。

我找不到语言参考的在线版本,但你可以在这里阅读


编辑:最初误解了这个问题,努力在c#语言规范中找到正确的答案。 基本上IIRC通过考虑最合适的类型来选择方法,如果你没有将参数精确地转换为IEnumerable<> ,那么Bar(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 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; Bar(value); 

从文档 :

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

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

这是一个协方差问题。 List不是协变的,因此ListList之间没有隐式转换。

另一方面,从C#4开始, IEnumerable现在支持协方差,所以这适用:

 var value = Enumerable.Empty(); Bar(value); var value = new List().AsEnumerable(); Bar(value); var value = new List(); Bar((IEnumerable)value);