简体   繁体   English

使用字典和泛型创建嵌套的,JSON可序列化的对象结构时,.NET中的C#协方差问题

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM