代码示例:
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<T>(T source)
(因此,不编译)?
在解决重载问题时,编译器是否会考虑类型参数约束?
UPD
避免与数组混淆。任何IEnumerable<T>
的实现都会发生这种情况,例如:
var value = new List<FooImpl>();
UPD 2 。
@ ken2k提到了协方差。
但是,让我们忘记FooImpl
。这样:
var value = new List<IFoo>();
Bar(value);
产生相同的错误
我确定,List<IFoo>
和IEnumerable<IFoo>
之间存在隐式转换,因为我可以轻松地写出这样的内容:
static void SomeMethod(IEnumerable<IFoo> sequence) {}
并将value
传递给它:
SomeMethod(value);
答案 0 :(得分:3)
在解决重载问题时,编译器是否会考虑类型参数约束?
不,因为泛型约束不是函数签名的一部分。您可以通过添加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
}
你的代码没有编译的原因是因为编译器会选择&#34; best&#34;基于方法签名的匹配,然后尝试应用通用约束。
不的一个可能原因是因为此调用不明确:
{假设List<T>
有Add<T>(IEnumerable<T> 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
方法使用不同的名称(如使用Add
和AddRange
在BCL中所做的那样。
答案 1 :(得分:2)
编辑:好的,在传递列表时选择Bar<T>(T source)
超过Bar<T>(IEnumerable<T> source)
的原因是因为&#34; 7.5.3.2 Better function member
&#34; C#语言参考部分。它说的是,当必须发生重载解析时,参数类型与适用的函数成员的参数类型匹配(见第7.5.3.1节),并且更好的函数成员由以下规则集选择:
•对于每个参数,从EX到QX的隐式转换并不比从EX到PX的隐式转换更好,并且
•对于至少一个参数,从EX到PX的转换优于从EX到QX的转换。
(PX是第一种方法的参数类型,第二种方法的QX)
此规则在扩展和类型参数替换&#34; 之后应用&#34;由于类型参数替换会将Bar(T源)交换为Bar&gt;(IList源),因此此方法参数将比需要转换的Bar(IEnumerable源)更好地匹配。
我无法找到该语言参考的在线版本,但您可以阅读here
IEnumerable<>
,那么Bar<T>(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的签名匹配,你必须明确地转换它
答案 2 :(得分:0)
这是一个covariance问题。 List<T>
不是协变的,因此List<FooImpl>
和List<IFoo>
之间没有隐式转换。
另一方面,从C#4开始,IEnumerable<T>
现在支持协方差,所以这有效:
var value = Enumerable.Empty<FooImpl>();
Bar(value);
var value = new List<FooImpl>().AsEnumerable();
Bar(value);
var value = new List<FooImpl>();
Bar((IEnumerable<IFoo>)value);
答案 3 :(得分:0)
从 c#7.3 开始,出于重载解析的目的,现在将泛型约束视为方法签名的一部分。来自What's new in C# 7.0 through C# 7.3: Improved overload candidates:
- 当方法组包含一些类型参数不满足其约束的通用方法时,这些成员将从候选集中删除。
因此,在c# 7.3 / .Net Core 2.x和更高版本中,问题编译并成功运行中显示的代码,并且根据需要调用Bar<T>(IEnumerable<T> value)
。