[英]Issues with C# Covariance in .NET when create a nested, JSON serializable object structure using Dictionaries & Generics
I am experiencing some issues in creating a nested object structure in C# using Dictionaries & Generics (I am using Visual Studio, .NET Framework 4.6+) 使用字典和泛型在C#中创建嵌套对象结构时遇到一些问题(我使用的是Visual Studio,.NET Framework 4.6+)
The main problem is the absence of covariance in C# Dictionaries. 主要问题是C#词典中缺少协方差。
I have to create this simple (JSON serializable/deserializable) object structure in C#. 我必须在C#中创建此简单的(JSON可序列化/可反序列化)对象结构。 I try to explain using the animals...
我试着用动物来解释...
public class AnimalCatalog
{
public Dictionary<string, Animal> AnimalsDict { get; set; } // key is the species name
}
public class Animal // base class
{
public string Species { get; set; } // univocal
public bool CanFly { get; set; }
public Dictionary<string, GenericPaw> PawsDict { get; set; } // each animal has a different type and a different number of paws
}
public class GenericPaw // base class
{
public int FingerNumber { get; set; } // number of finger in each paw
}
public class Cat : Animal // derived class
{
public void meow() // only cats says 'meow'
{...}
}
public class CatPaw : GenericPaw // derived class
{
public void scratch() // cats paws could scratch something :) but not all the animals paws could
{...}
}
I implemented this structure using C# generics, because a Cat has a dictionary of CatPaws, not generic Paws :P. 我使用C#泛型实现了这种结构,因为Cat拥有CatPaws的字典,而不是泛型Paws:P。 this is my proposal.
这是我的建议。
public class AnimalCatalog<T,V> where T : Animal<V> where V : GenericPaw
{
public Dictionary<string, T> AnimalsDict { get; set; } = new Dictionary<string, T>(); // key is the species name
}
public class Animal<T> where T : GenericPaw // base class
{
public string Species { get; set; } // univocal
public bool CanFly { get; set; }
public Dictionary<string, T> PawsDict { get; set; } // each animal has a different type and a different number of paws
}
public class GenericPaw // base class
{
public string PawName { get; set; } // univocal
public int FingerNumber { get; set; } // number of finger in each paw
}
public class Cat<T> : Animal<T> where T : CatPaw // derived class
{
public void meow() // only cats says 'meow'
{...}
}
public class CatPaw : GenericPaw // derived class
{
public void scratch() // cats paws could scratch something :) but not all the animals paws could
{...}
}
let's use the created class 让我们使用创建的类
Cat<CatPaw> Kitty = new Cat<CatPaw>(); // create a cat
CatPaw KittyFirstPaw = new CatPaw(); // create the 1st cat's paw
Kitty.PawsDict.Add(KittyFirstPaw.PawName, KittyFirstPaw); // add the paw to the dict
AnimalCatalog<Animal<GenericPaw>,GenericPaw> ZooCatalog = new AnimalCatalog<Animal<GenericPaw>,GenericPaw>(); // create a catalog of animals
Animal<GenericPaw> GenericAnimal = Kitty; <-- doens't compile (can't convert derived to base class)
AnimalCatalog.AnimalsDict.Add(GenericAnimal.Species, GenericAnimal);
I also tried using an interface instead of a base class, using the out keyword to specify T as a covariant type, but it doesn't work because I can't use a covariant type in a dict... 我还尝试使用接口而不是基类,使用out关键字将T指定为协变类型,但是它不起作用,因为我不能在字典中使用协变类型。
Any help is very appreciated :) Stefano 任何帮助都非常感激:) Stefano
You can't convert Cat<CatPaw>
to Animal<GenericPow>
, because then you could add a different kind of GenericPaw
into its dictionary (for example a DogPaw
) and the cat wouldn't appreciate that. 您不能将
Cat<CatPaw>
转换为Animal<GenericPow>
,因为那样的话您可以在其字典中添加另一种GenericPaw
(例如DogPaw
),而猫则不会喜欢。
This is only a problem because you can insert new values in the dictionary, so it seems like it could be solved by using an IReadOnlyDictionary
, but unfortunately that one isn't covariant either because of technical issues as described in this question . 这只是一个问题,因为您可以在字典中插入新值,因此似乎可以通过使用
IReadOnlyDictionary
来解决,但不幸的是,由于该问题中所述的技术问题,它也不是协变的。
On a side note, is there a reason why Cat
is also generic? 附带说明一下,
Cat
也是通用的吗?
public class Cat<T> : Animal<T> where T : CatPaw
That would be useful if you want to create a cat that can only have certain specific paws derived from CatPaw
. 如果您要创建只具有从
CatPaw
派生的某些特定爪子的猫,那将很有用。 If not, this could become: 如果没有,则可能变为:
public class Cat : Animal<CatPaw>
The AnimalCatalog
also seems like it's unnecessarily complex. AnimalCatalog
似乎也不必要地复杂。 If you only ever need an animal catalog of Animal
, it could be simplified to just one generic parameter: 如果您只需要
Animal
的动物目录,可以将其简化为一个通用参数:
public class AnimalCatalog<TPaw> where TPaw : GenericPaw
{
public Dictionary<string, Animal<TPaw>> AnimalsDict { get; set; }
}
and if you only ever need an AnimalCatalog<GenericPaw>
, you could get rid of the one parameter too. 如果只需要
AnimalCatalog<GenericPaw>
,那么您也可以摆脱一个参数。 But it's still the case that this catalog would not be very useful since you can't convert an Animal<CatPaw>
to Animal<GenericPaw>
. 但是仍然存在这种情况,因为您无法将
Animal<CatPaw>
转换为Animal<GenericPaw>
,因此该目录不是很有用。
To solve this, you could create a convariant interface of IAnimal<out TPaw>
that has all the properties of an Animal
but instead of the dictionary, you could either expose paws as an IEnumerable<TPaw> Paws
and if you need the dictionary lookup, a method: TPaw GetPaw(string pawName)
. 为了解决这个问题,您可以创建一个IAnimal
IAnimal<out TPaw>
的变数接口,该接口具有Animal
所有属性,但是可以代替字典,而可以将爪子公开为IEnumerable<TPaw> Paws
并且如果需要字典查找,一个方法: TPaw GetPaw(string pawName)
。 These would be implemented using the dictionary. 这些将使用字典来实现。 Then it's possible to convert a
Cat
( which is an Animal<CatPaw>
and therefore also IAnimal<CatPaw>
) to IAnimal<GenericPaw>
. 然后可以将
Cat
(这是Animal<CatPaw>
,因此也是IAnimal<CatPaw>
)转换为IAnimal<GenericPaw>
。 Your animal catalog will then contain IAnimal<GenericPaw>
. 您的动物目录将包含
IAnimal<GenericPaw>
。
Only Interfaces and Delegates allow Covariance. 仅接口和代理允许协方差。 See Microsoft Docs .
请参阅Microsoft Docs 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.