IDictionary<,> contravariance?

Solution wise you could do something like this, just pass in an accessor instead:

    public static void DoStuffWithAnimals(Func<string, Animal> getAnimal)
    {
    }

    var dicLions = new Dictionary<string, Lion>();
    DoStuffWithAnimals(s => dicLions[s]);

Obviously that is likely to be a bit simple for your needs, but if you only need a couple of the dictionary methods it's pretty easy to get that in place.

This is another way that gives you a bit of code re-use between your animals:

    public class Accessor<T> : IAnimalAccessor where T : Animal
    {
        private readonly Dictionary<string, T> _dict;

        public Accessor(Dictionary<string, T> dict)
        {
            _dict = dict;
        }

        public Animal GetItem(String key)
        {
            return _dict[key];
        }
    }

    public interface IAnimalAccessor
    {
        Animal GetItem(string key);
    }

    public static void DoStuffWithAnimals(IAnimalAccessor getAnimal)
    {
    }

    var dicLions = new Dictionary<string, Lion>();
    var accessor = new Accessor<Lion>(dicLions);
    DoStuffWithAnimals(accessor);

Suppose that Derived is a subtype of Base. Then, Dictionary<Base> can't be a subtype of Dictionary<Derived> because you can't put any object of type Base into a Dictionary<Derived>, but Dictionary<Derived> can't be a subtype of Dictionary<Base> because if you get an object out of a Dictionary<Base>, it might not be a Derived. Therefore, Dictionary is neither co- nor contravariant in its type parameter.

Generally, collections that you can write to are invariant for this reason. If you have an immutable collection, then it could be covariant. (If you had some sort of write-only collection, itmight be contravariant.)

EDIT: And if you had a "collection" that you could neither get data from nor put data into, then it might be both co- and contravariant. (It would also probably be useless.)


You could modify public static void DoStuffWithAnimals(IDictionary<string, Animal> animals) to add a generic constraint. Here is something that works for me in LINQPad:

void Main()
{
    var lions = new Dictionary<string, Lion>();
    lions.Add("one", new Lion{Name="Ben"});
    AnimalManipulator.DoStuffWithAnimals(lions);
}

public class AnimalManipulator
{
    public static void DoStuffWithAnimals<T>(IDictionary<string, T> animals)
    where T : Animal
    {
        foreach (var kvp in animals)
        {
            kvp.Value.MakeNoise();
        }
    }
}

public class Animal
{
    public string Name {get;set;}
    public virtual string MakeNoise()
    {
        return "?";
    }
}

public class Lion : Animal
{
    public override string MakeNoise()
    {
        return "Roar";
    }
}

Firstly, covariance and contravariance in C# only apply to interfaces and delegates.

So your question is really about IDictionary<TKey,TValue>.

With that out of the way, it's simplest to just remember that an interface can only be co/contra-variant if all values of a type parameter are either only passed in, or only passed out.

For example (contravariance):

interface IReceiver<in T> // note 'in' modifier
{
    void Add(T item);
    void Remove(T item);
}

And (covariance):

interface IGiver<out T> // note 'out' modifier
{
    T Get(int index);
    T RemoveAt(int index);
}

In the case of IDictionary<TKey,TValue>, both type parameters are used in both an in and out capacity, meaning that the interface cannot be covariant or contravariant. It is invariant.

However, the class Dictionary<TKey,TValue> does implement IEnumerable<T> which is covariant.

A great reference for this is:

https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance

Tags:

C#

.Net