C# 为什么泛型类型约束会导致无隐式引用转换错误?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17434851/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why does a generic type constraint result in a no implicit reference conversion error?
提问by Rens
I have created a couple of interfaces and generic classes for working with agenda appointments:
我创建了几个用于处理议程约会的接口和通用类:
interface IAppointment<T> where T : IAppointmentProperties
{
T Properties { get; set; }
}
interface IAppointmentEntry<T> where T : IAppointment<IAppointmentProperties>
{
DateTime Date { get; set; }
T Appointment { get; set; }
}
interface IAppointmentProperties
{
string Description { get; set; }
}
class Appointment<T> : IAppointment<T> where T : IAppointmentProperties
{
public T Properties { get; set; }
}
class AppointmentEntry<T> : IAppointmentEntry<T> where T : IAppointment<IAppointmentProperties>
{
public DateTime Date { get; set; }
public T Appointment { get; set; }
}
class AppointmentProperties : IAppointmentProperties
{
public string Description { get; set; }
}
I'm trying to use some constraints on the type parameters to ensure that only valid types can be specified. However, when specifying a constraint defining that Tmust implement IAppointment<IAppointmentProperties>, the compiler gives an error when using a class that is Appointment<AppointmentProperties>:
我正在尝试对类型参数使用一些约束,以确保只能指定有效类型。但是,当指定定义T必须实现的约束时IAppointment<IAppointmentProperties>,编译器在使用以下类时会给出错误Appointment<AppointmentProperties>:
class MyAppointment : Appointment<MyAppointmentProperties>
{
}
// This goes wrong:
class MyAppointmentEntry : AppointmentEntry<MyAppointment>
{
}
class MyAppointmentProperties : AppointmentProperties
{
public string ExtraInformation { get; set; }
}
The error is:
错误是:
The type 'Example.MyAppointment' cannot be used as type parameter 'T' in the generic type or method 'Example.AppointmentEntry<T>'. There is no implicit reference conversion from 'Example.MyAppointment' to 'Example.IAppointment<Example.IAppointmentProperties>'.
The type 'Example.MyAppointment' cannot be used as type parameter 'T' in the generic type or method 'Example.AppointmentEntry<T>'. There is no implicit reference conversion from 'Example.MyAppointment' to 'Example.IAppointment<Example.IAppointmentProperties>'.
Could anybody explain why this does not work?
有人可以解释为什么这不起作用吗?
采纳答案by Eric Lippert
Let's simplify:
让我们简化一下:
interface IAnimal { ... }
interface ICage<T> where T : IAnimal { void Enclose(T animal); }
class Tiger : IAnimal { ... }
class Fish : IAnimal { ... }
class Cage<T> : ICage<T> where T : IAnimal { ... }
ICage<IAnimal> cage = new Cage<Tiger>();
Your question is: why is the last line illegal?
你的问题是:为什么最后一行是非法的?
Now that I have rewritten the code to simplify it, it should be clear. An ICage<IAnimal>is a cage into which you can place any animal, but a Cage<Tiger>can only hold tigers, so this must be illegal.
现在我已经重写了代码以简化它,应该很清楚了。 一个ICage<IAnimal>在一个笼子可以在其中放置任何动物,但Cage<Tiger>可以只持有虎,所以这一定是非法的。
If it were not illegal then you could do this:
如果它不违法,那么你可以这样做:
cage.Enclose(new Fish());
And hey, you just put a fish into a tiger cage.
嘿,你只是把一条鱼放进老虎笼子里。
The type system does not permit that conversion because doing so would violate the rule that the capabilities of the source type must not be lessthan the capabilities of the target type. (This is a form of the famous "Liskov substitution principle".)
类型系统不允许这种转换,因为这样做会违反源类型的能力不能小于目标类型的能力的规则。(这是著名的“里氏替换原则”的一种形式。)
More specifically, I would say that you are abusing generics. The fact that you've made type relationships that are too complicated for you to analyze yourself is evidence that you ought to simplify the whole thing; if you're not keeping all the type relationships straight and you wrote the thing then your users surely will not be able to keep it straight either.
更具体地说,我会说你在滥用泛型。您已经建立了过于复杂的类型关系,以至于您无法自己分析这一事实,这一事实证明您应该简化整个事情;如果您没有保持所有类型关系直截了当并且您编写了内容,那么您的用户肯定也无法保持直截了当。
回答by Peter Gluck
Because you declared your MyAppointmentclass using the concrete type rather than the interface. You should declare as follows:
因为您MyAppointment使用具体类型而不是接口声明了您的类。您应该声明如下:
class MyAppointment : Appointment<IAppointmentProperties> {
}
Now the conversion can occur implicitly.
现在转换可以隐式发生。
By declaring AppointmentEntry<T>with the constraint where T: IAppointment<IAppointmentProperties>you are creating a contractwhereby the unspecified type for AppointmentEntry<T>mustaccommodate any type that is declared with IAppointmentProperties. By declaring the type with the concrete class you have violated that contract (it implements atype of IAppointmentPropertiesbut not anytype).
通过AppointmentEntry<T>使用约束进行声明,where T: IAppointment<IAppointmentProperties>您将创建一个契约,其中未指定的类型AppointmentEntry<T>必须容纳使用IAppointmentProperties. 通过使用具体类声明类型,您违反了该约定(它实现了一种类型,IAppointmentProperties但不是任何类型)。
回答by Kaginawa
It will work if you re-define the sample interface from:
如果您从以下位置重新定义示例界面,它将起作用:
interface ICage<T>
to
到
interface ICage<out T>
(please notice the outkeyword)
(请注意out关键字)
then the following statement is correct:
那么下面的说法是正确的:
ICage<IAnimal> cage = new Cage<Tiger>();
回答by Stephen Zeng
There is already a very good answer by Eric. Just wanted to take this chance to talk about the Invariance, Covarianceand Contravariancehere.
埃里克已经有一个很好的答案。只是想借此机会在这里谈谈Invariance、Covariance和Contravariance。
For definitions please see https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance
有关定义,请参阅https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance
Let's say there is a zoo.
假设有一个动物园。
abstract class Animal{}
abstract class Bird : Animal{}
abstract class Fish : Animal{}
class Dove : Bird{}
class Shark : Fish{}
The zoo is relocating, so its animals need to be moved from the old zoo to the new one.
动物园正在搬迁,所以它的动物需要从旧动物园搬到新动物园。
Invariance
不变性
Before we move them, we need to put the animals into different containers. The containers all do the same operations: put an animal in it or get an animal out from it.
在移动它们之前,我们需要将动物放入不同的容器中。这些容器都执行相同的操作:将动物放入其中或从中取出动物。
interface IContainer<T> where T : Animal
{
void Put(T t);
T Get(int id);
}
Obviously for fish we need a tank:
显然,对于鱼,我们需要一个坦克:
class FishTank<T> : IContainer<T> where T : Fish
{
public void Put(T t){}
public T Get(int id){return default(T);}
}
So the fish can be put in and get out from the tank(hopefully still alive):
因此可以将鱼放入并从水箱中取出(希望还活着):
IContainer<Fish> fishTank = new FishTank<Fish>(); //Invariance, the two types have to be the same
fishTank.Put(new Shark());
var fish = fishTank.Get(8);
Suppose we are allowedto change it to IContainer<Animal>, then you can accidentally put a dove in the tank, which obliviously tragedy will occur.
假设我们允许将其更改为IContainer<Animal>,那么您可能会不小心将一只鸽子放入坦克中,这显然会发生悲剧。
IContainer<Animal> fishTank = new FishTank<Fish>(); //Wrong, some animal can be killed
fishTank.Put(new Shark());
fishTank.Put(new Dove()); //Dove will be killed
Contravariance
逆变
In order to improve the effeciency, the zoo management team dicides to separate the load and unload process (management always do this). So we have two separate operations, one for load only, the other unload.
为了提高效率,动物园管理团队决定将装载和卸载过程分开(管理总是这样做)。所以我们有两个单独的操作,一个只用于加载,另一个用于卸载。
interface ILoad<in T> where T : Animal
{
void Put(T t);
}
Then we have a bird cage:
然后我们有一个鸟笼:
class BirdCage<T> : ILoad<T> where T : Bird
{
public void Put(T t)
{
}
}
ILoad<Bird> normalCage = new BirdCage<Bird>();
normalCage.Put(new Dove()); //accepts any type of birds
ILoad<Dove> doveCage = new BirdCage<Bird>();//Contravariance, Bird is less specific then Dove
doveCage.Put(new Dove()); //only accepts doves
Covariance
协方差
The in the new zoo we have a team for unloading animals.
在新的动物园里,我们有一个卸载动物的团队。
interface IUnload<out T> where T : Animal
{
IEnumerable<T> GetAll();
}
class UnloadTeam<T> : IUnload<T> where T : Animal
{
public IEnumerable<T> GetAll()
{
return Enumerable.Empty<T>();
}
}
IUnload<Animal> unloadTeam = new UnloadTeam<Bird>();//Covariance, since Bird is more specific then Animal
var animals = unloadTeam.GetAll();
From the team's point of view, it does not matter what it is inside, they just unload the animals from the containers.
从团队的角度来看,里面是什么并不重要,他们只是从容器中卸下动物。
回答by CitrusO2
In case someone else also has this error message: I found the same interface defined twice in different namespaces and the classes that have been tryed to be linked together did not use the same interface.
如果其他人也有此错误消息:我发现在不同的命名空间中定义了两次相同的接口,并且尝试链接在一起的类没有使用相同的接口。

