Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IDictionary<,> contravariance?

Tags:

c#

.net

I have the following method in an external class

public static void DoStuffWithAnimals(IDictionary<string, Animal> animals)

In my calling code, I already have a Dictionary<string, Lion> object, but I can't pass this in as this method's argument. So a IDictionary<,> isn't contravariant? I can't see any reason why this shouldn't work.

The only solution I can think of is:

var animals = new Dictionary<string, Animal>();

foreach(var kvp in lions) {
    animals.Add(kvp.Key, kvp.Value);
}

Is there no way to pass this dictionary into this method, without having to create a new dictionary of the same objects?


EDIT:

As it's my method, I know that the only member I'm using from the dictionary is the getter of TValue this[TKey key], which is a member of IDictionary<TKey, TValue>, so in this scenario, I'm unable to use a 'wider' type for the parameter.

like image 215
Connell Avatar asked Oct 11 '12 14:10

Connell


People also ask

What is the difference between covariance and Contravariance?

In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments. Covariance preserves assignment compatibility and contravariance reverses it.

What is covariance and Contravariance in generics?

Covariance and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types.

Why is contravariance useful?

This is mainly useful when using already defined standard interfaces. Covariance means that you can use IEnumerable<string> in place where IEnumerable<object> is expected. Contravariance allows you to pass IComparable<object> as an argument of a method taking IComparable<string> .

What is covariance and Contravariance in Java?

Covariance can be translated as "different in the same direction," or with-different, whereas contravariance means "different in the opposite direction," or against-different. Covariant and contravariant types are not the same, but there is a correlation between them. The names imply the direction of the correlation.


2 Answers

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

like image 118
Drew Noakes Avatar answered Sep 28 '22 10:09

Drew Noakes


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);
like image 23
Andrew Barrett Avatar answered Sep 28 '22 12:09

Andrew Barrett