为什么generics类型约束会导致无隐式引用转换错误?
我创建了几个用于处理议程约会的接口和generics类:
interface IAppointment where T : IAppointmentProperties { T Properties { get; set; } } interface IAppointmentEntry where T : IAppointment { DateTime Date { get; set; } T Appointment { get; set; } } interface IAppointmentProperties { string Description { get; set; } } class Appointment : IAppointment where T : IAppointmentProperties { public T Properties { get; set; } } class AppointmentEntry : IAppointmentEntry where T : IAppointment { public DateTime Date { get; set; } public T Appointment { get; set; } } class AppointmentProperties : IAppointmentProperties { public string Description { get; set; } }
我正在尝试对类型参数使用一些约束,以确保只能指定有效类型。 但是,在指定定义T
必须实现IAppointment
的约束时,编译器在使用Appointment
的类时会出错:
class MyAppointment : Appointment { } // This goes wrong: class MyAppointmentEntry : AppointmentEntry { } class MyAppointmentProperties : AppointmentProperties { public string ExtraInformation { get; set; } }
错误是:
The type 'Example.MyAppointment' cannot be used as type parameter 'T' in the generic type or method 'Example.AppointmentEntry'. There is no implicit reference conversion from 'Example.MyAppointment' to 'Example.IAppointment'.
任何人都可以解释为什么这不起作用?
我们简化一下:
interface IAnimal { ... } interface ICage where T : IAnimal { void Enclose(T animal); } class Tiger : IAnimal { ... } class Fish : IAnimal { ... } class Cage : ICage where T : IAnimal { ... } ICage cage = new Cage();
你的问题是:为什么最后一行是非法的?
既然我已经重写了代码以简化它,那么应该很清楚。 ICage
是一个可以放置任何动物的Cage
,但是Cage
只能容纳老虎 ,所以这必须是非法的。
如果它不是非法的那么你可以这样做:
cage.Enclose(new Fish());
嘿,你只是把鱼放进虎笼里。
类型系统不允许转换,因为这样做会违反源类型的function不得小于目标类型的function的规则。 (这是着名的“Liskov替代原则”的一种forms。)
更具体地说,我会说你滥用generics。 事实上,你已经建立了太复杂的类型关系,你无法分析自己,这certificate你应该简化整个事情; 如果你没有保持所有的类型关系并且你写了这个东西那么你的用户肯定也无法保持直接。
Eric已经有了一个非常好的答案。 只想借此机会在这里讨论不变性 , 协方差和逆变性 。
有关定义,请参阅https://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx
假设有一个动物园。
abstract class Animal{} abstract class Bird : Animal{} abstract class Fish : Animal{} class Dove : Bird{} class Shark : Fish{}
动物园正在重新安置,所以它的动物需要从旧动物园搬到新动物园。
不变性
在我们移动它们之前,我们需要将动物放入不同的容器中。 容器都做同样的操作:将动物放入其中或从中取出动物。
interface IContainer where T : Animal { void Put(T t); T Get(int id); }
显然对于鱼我们需要一个坦克:
class FishTank : IContainer where T : Fish { public void Put(T t){} public T Get(int id){return default(T);} }
所以鱼可以放入并从水箱中出来(希望还活着):
IContainer fishTank = new FishTank (); //Invariance, the two types have to be the same fishTank.Put(new Shark()); var fish = fishTank.Get(8);
假设我们被允许将它改为IContainer
,那么你可能会意外地将一只鸽子放入坦克中,这将发生不经意的悲剧。
IContainer fishTank = new FishTank(); //Wrong, some animal can be killed fishTank.Put(new Shark()); fishTank.Put(new Dove()); //Dove will be killed
逆变
为了提高效率,动物园管理团队决定将加载和卸载过程分开(管理总是这样做)。 所以我们有两个独立的操作,一个只用于加载,另一个用于卸载。
interface ILoad where T : Animal { void Put(T t); }
然后我们有一个鸟笼:
class BirdCage : ILoad where T : Bird { public void Put(T t) { } } ILoad normalCage = new BirdCage (); normalCage.Put(new Dove()); //accepts any type of birds ILoad doveCage = new BirdCage();//Contravariance, Bird is less specific then Dove doveCage.Put(new Dove()); //only accepts doves
协方差
在新的动物园里,我们有一个卸载动物的团队。
interface IUnload where T : Animal { IEnumerable GetAll(); } class UnloadTeam : IUnload where T : Animal { public IEnumerable GetAll() { return Enumerable.Empty (); } } IUnload unloadTeam = new UnloadTeam();//Covariance, since Bird is more specific then Animal var animals = unloadTeam.GetAll();
从团队的角度来看,里面的东西并不重要,他们只是从容器中卸下动物。
因为您使用具体类型而不是接口声明了MyAppointment
类。 您应该声明如下:
class MyAppointment : Appointment { }
现在转换可以隐式发生。
通过使用约束来声明AppointmentEntry
, where T: IAppointment
您正在创建一个契约 ,其中AppointmentEntry
的未指定类型必须适应使用IAppointmentProperties
声明的任何类型。 通过使用具体类声明类型,您违反了该合同(它实现了一种IAppointmentProperties
但不是任何类型)。
如果您从以下位置重新定义示例接口,它将起作用:
interface ICage
至
interface ICage
(请注意out
关键字)
那么以下陈述是正确的:
ICage cage = new Cage();