2

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+)

The main problem is the absence of covariance in C# Dictionaries.

I have to create this simple (JSON serializable/deserializable) object structure in C#. 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. 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...

Any help is very appreciated :) Stefano

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
Stefano
  • 21
  • 4

2 Answers2

3

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.

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.

On a side note, is there a reason why Cat is also generic?

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. If not, this could become:

public class Cat : Animal<CatPaw>

The AnimalCatalog also seems like it's unnecessarily complex. If you only ever need an animal catalog of Animal, it could be simplified to just one generic parameter:

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. 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>.

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). 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>. Your animal catalog will then contain IAnimal<GenericPaw>.

Neme
  • 492
  • 3
  • 14
0

Only Interfaces and Delegates allow Covariance. See Microsoft Docs.

keuleJ
  • 3,418
  • 4
  • 30
  • 51
  • GenericPaw already looks like an interface, so it seems OP was on the right track, just not there yet. – Mixxiphoid Jan 02 '18 at 12:52
  • Yes, right, I tried to use interfaces instead of base classes. Using generics types with the 'out' keyword (to specify covariant type) is not allowed if type is used in a dictionary :( – Stefano Jan 02 '18 at 13:01
  • @Mixxiphoid its Animal that shoud be an Interface. – keuleJ Jan 02 '18 at 13:06
  • @keuleJ true, I think both should be interfaces. The name of GenericPaw was already hinting that is should be interface. – Mixxiphoid Jan 02 '18 at 13:09
  • @Stefano Why isn't is just an IEnumerable? The type could be a property of the Ipaw. – Mixxiphoid Jan 02 '18 at 13:10
  • @ Mixxiphoid IEnumerable is read only and don't allow me to use dictionary, that is very important for the animals in the catalog :) – Stefano Jan 02 '18 at 13:46