Search code examples
c#inheritancecovariance

Can I create derived collections of a derived type cleanly?


I have three classes:

  • Animal
  • Giraffe : Animal
  • Tiger : Animal

... and want to implement three dictionary classes:

public abstract class AnimalDictionary
{
    internal Dictionary<int, Animal> _internalDictionary;

    //TODO: Dictionary interface implementation, minus the "Add" method
}

public class GiraffeDictionary : AnimalDictionary, IDictionary<int, Giraffe>
{
    //Collection Code Examples
    public List<Giraffe> GetGiraffesWithHerdID(int herdID);
    public void FleeFromTigers(TigerDictionary pride);
}

public class TigerDictionary : AnimalDictionary, IDictionary<int, Tiger>
{
    //Collection Code Examples
    public List<Tiger> GetActiveHunters();
    public void HuntGiraffeHerd(GiraffeDictionary targetHerd);
}

I'm well aware that the base AnimalDictionary cannot implement IDictionary or ICollection because of the problems being able to put a Tiger in a GiraffeDictionary would cause. I won't pretend to understand properly covariance and contravariance, but I do get why you can't put tigers in the giraffe pen.

Instead, I've implemented AnimalDictionary by having it inherit IEnumerable<KeyValuePair<int, Animal>>, and just made it implement all the same methods as a dictionary, minus Add(), since Add() will be implemented in the derived classes.

Unfortunately, implementing IDictionary in the derived collection classes has given me this bit of ugliness in GiraffeDictionary and TigerDictionary:

  • ICollection<Giraffe> IDictionary<int, Giraffe>.Values => _internalDictionary.Values.Cast<Giraffe>().ToList();

This concerns me, since I expect to be iterating over these collections in tight loops. I want to be able to iterate over the Values collection without the performance cost of casting and creating a whole new list, but Values is an ICollection, not an IEnumerable.

How can I resolve this problem? Is there an alternative approach that I'm missing, or should I give up on having a base class entirely and just accept that I'll have duplicate methods in the derived collection classes?


Solution

  • I think the generic AnimalDictionary<T> will be helpful here and we can write the common method in the generic class which can be re-used by the other derived types. Here is a sample I composed which should give some idea how to approach this:

    public class AnimalDictionary<T> where T : Animal
    {
        
    }
    

    We can define the common generic method in the AnimalDictionary<T> type so that we don't have two duplicate the common behaviors in the other types of Animal

    and inside the generic type we can have a generic dictionary field like:

    IDictionary<int,T> animalDictionary;
    

    Here are the related stubs:

    public class Animal
    {
    }
    
    public class Cat : Animal
    {
    }
    

    and the calling code would be like:

    public static void Main()
    {
        AnimalDictionary<Cat> catDictionary = new AnimalDictionary<Cat>();
        Console.WriteLine("Hello World");
    }
    

    Now we can add behaviors like this:

    public class AnimalDictionary<T> where T : Animal
    {
        private IDictionary<int,T> animalDictionary = new Dictionary<int,T>();
        
        public void AddAnimal(T animal)
        {
            animalDictionary.Add(animal);
        }
    }
    

    and here is the calling side code :

    public static void Main()
    {
        AnimalDictionary<Cat> animalDictionary = new AnimalDictionary<Cat>();
        animalDictionary.AddAnimal(new Cat());
        Console.WriteLine("Hello World");
    }