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.
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.
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.
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> .
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.
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
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With