C#多态与泛型类型匹配错误
C# polymorphism and generics type matching error
我有一个类似的结构
enum AnimalType {dog, cat}
class Animal{}
class Dog : Animal {}
class Cat : Animal {}
class Vet<T> where T : Animal {}
class DogVet : Vet<Dog> {}
class CatVet : Vet<Cat> {}
为什么我不能分配这个?
...
Vet<Animal> myVet = new DogVet();
...
为什么我不能像这样向 Dictionary
添加元素?
...
Dictionary<AnimalType, Vet<Animal>> _vetList = new Dictionary<AnimalType, Vet<Animal>>();
_vetList.Add(AnimalType.dog, new DogVet());
...
应该怎么做?
这是一个经典的协方差问题。 Vet<Dog>
不是 Vet<Animal>
。继续类比的话,一个Vet<Dog>
只能治疗狗。你不能把它当作可以治疗任何种动物的兽医。
假设您有一个 Treat
函数:
public void Treat<T>(T patient) {}
现在如果 Vet<Dog>
是 Vet<Animal>
那么这将是可能的:
Vet<Animal> dogVet = new Vet<Dog>();
dogVet.Treat(new Cat()); // but I can only treat dogs!!!
为此,您需要协变(或逆变)类型参数。不幸的是,您只能在接口或委托上指定这些,而不能在 classes 上指定。获得所需内容的一种方法是在 Vet class 之上创建一个界面,如下所示:
// Not sure what you need this enum for, but OK...
enum AnimalType {dog, cat}
class Animal{}
class Dog : Animal {}
class Cat : Animal {}
// Create an interface with covariant parameter
// i.e. an IVet<T> is also an IVet<Animal> for all T deriving from Animal.
interface IVet<out T> where T : Animal {}
// Made Vet abstract. You can still use this to provide base implementations for concrete Vets.
abstract class Vet<T> : IVet<T> where T : Animal {}
class DogVet : Vet<Dog> {}
class CatVet : Vet<Cat> {}
static void Main()
{
// Must use the interface here.
IVet<Animal> vet = new DogVet();
}
老实说,您发布的代码让我怀疑问题是否出在代码的设计上,而不是语法上。
请注意,尽管可以编译,但 D Stanley 的回答中的警告是有效的。例如,您现在无法向接口添加方法 void Treat(T patient)
。
当你发现自己正在检查 运行 时间类型,或者因为你的基础 class 定义了一个以 T
作为参数的函数并且它不会接受而出现编译错误的那一刻在派生的 class 中带有 Dog
的实现,您应该重新设计您的程序。事实上,我现在会认真考虑。
这里的代码味道是你在 Animal
上有一个继承树,你正在用另一个继承树 Vet
模仿它。未能为新的 Animal
衍生品添加适当的 Vet
衍生品会让您头疼。
主要原因是泛型不支持协变。以下信息摘自 Jon Skeet 的书 c# In Depth。
简短的回答是因为这是合法的,但如果被允许则无效。
Dictionary<AnimalType, Vet<Animal>> _vetList = new Dictionary<AnimalType, Vet<Cat>>();
_vetList.Add(AnimalType.Dog, new Vet<Dog>());
由于字典被告知它使用的是动物,但实际对象是猫,因此它会在运行时失败。再次根据这本书,设计者宁愿有编译时失败而不是运行时崩溃,所以当你尝试这样做时你会得到一个错误。
这称为协方差,此功能仅在委托和接口中受支持。
例如:
// Covariance
IEnumerable<object> x = new List<string>();
了解有关协变和逆变的更多信息in MSDN。
我有一个类似的结构
enum AnimalType {dog, cat}
class Animal{}
class Dog : Animal {}
class Cat : Animal {}
class Vet<T> where T : Animal {}
class DogVet : Vet<Dog> {}
class CatVet : Vet<Cat> {}
为什么我不能分配这个?
...
Vet<Animal> myVet = new DogVet();
...
为什么我不能像这样向 Dictionary
添加元素?
...
Dictionary<AnimalType, Vet<Animal>> _vetList = new Dictionary<AnimalType, Vet<Animal>>();
_vetList.Add(AnimalType.dog, new DogVet());
...
应该怎么做?
这是一个经典的协方差问题。 Vet<Dog>
不是 Vet<Animal>
。继续类比的话,一个Vet<Dog>
只能治疗狗。你不能把它当作可以治疗任何种动物的兽医。
假设您有一个 Treat
函数:
public void Treat<T>(T patient) {}
现在如果 Vet<Dog>
是 Vet<Animal>
那么这将是可能的:
Vet<Animal> dogVet = new Vet<Dog>();
dogVet.Treat(new Cat()); // but I can only treat dogs!!!
为此,您需要协变(或逆变)类型参数。不幸的是,您只能在接口或委托上指定这些,而不能在 classes 上指定。获得所需内容的一种方法是在 Vet class 之上创建一个界面,如下所示:
// Not sure what you need this enum for, but OK...
enum AnimalType {dog, cat}
class Animal{}
class Dog : Animal {}
class Cat : Animal {}
// Create an interface with covariant parameter
// i.e. an IVet<T> is also an IVet<Animal> for all T deriving from Animal.
interface IVet<out T> where T : Animal {}
// Made Vet abstract. You can still use this to provide base implementations for concrete Vets.
abstract class Vet<T> : IVet<T> where T : Animal {}
class DogVet : Vet<Dog> {}
class CatVet : Vet<Cat> {}
static void Main()
{
// Must use the interface here.
IVet<Animal> vet = new DogVet();
}
老实说,您发布的代码让我怀疑问题是否出在代码的设计上,而不是语法上。
请注意,尽管可以编译,但 D Stanley 的回答中的警告是有效的。例如,您现在无法向接口添加方法 void Treat(T patient)
。
当你发现自己正在检查 运行 时间类型,或者因为你的基础 class 定义了一个以 T
作为参数的函数并且它不会接受而出现编译错误的那一刻在派生的 class 中带有 Dog
的实现,您应该重新设计您的程序。事实上,我现在会认真考虑。
这里的代码味道是你在 Animal
上有一个继承树,你正在用另一个继承树 Vet
模仿它。未能为新的 Animal
衍生品添加适当的 Vet
衍生品会让您头疼。
主要原因是泛型不支持协变。以下信息摘自 Jon Skeet 的书 c# In Depth。
简短的回答是因为这是合法的,但如果被允许则无效。
Dictionary<AnimalType, Vet<Animal>> _vetList = new Dictionary<AnimalType, Vet<Cat>>();
_vetList.Add(AnimalType.Dog, new Vet<Dog>());
由于字典被告知它使用的是动物,但实际对象是猫,因此它会在运行时失败。再次根据这本书,设计者宁愿有编译时失败而不是运行时崩溃,所以当你尝试这样做时你会得到一个错误。
这称为协方差,此功能仅在委托和接口中受支持。
例如:
// Covariance
IEnumerable<object> x = new List<string>();
了解有关协变和逆变的更多信息in MSDN。