具有约束的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);
产生相同的错误。
我敢肯定, List
和IEnumerable
存在隐式转换,因为我可以轻松地编写如下内容:
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
)方法}
List
显而易见的解决方法是为采用集合的Bar
方法使用不同的名称(如使用Add
和AddRange
在BCL中完成的AddRange
。
编辑:好的,当传递List时, Bar
被选中而不是Bar
是因为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
将完全匹配参数类型,就像在这个样本中一样:
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
不是协变的,因此List
和List
之间没有隐式转换。
另一方面,从C#4开始, IEnumerable
现在支持协方差,所以这适用:
var value = Enumerable.Empty(); Bar(value); var value = new List ().AsEnumerable(); Bar(value); var value = new List (); Bar((IEnumerable)value);