Why does the following interface declaration produce an invalid variance error?
public interface ICache<in TKey, TValue>
{
TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory);
}
The compiler says:
Error CS1961 Invalid variance: The type parameter 'TKey' must be covariantly valid on 'ICache<TKey, TValue>.GetOrAdd(TKey, Func<TKey, TValue>)'. 'TKey' is contravariant.
The problem lies with the use of TKey
in the Func
parameter. But TKey
is being used as an input as Func
requires. Why must it be covariant?
TL;DR: The C# compiler is keeping you safe.
Effectively, "nested input in an input position makes an output" when it comes to generic variance. Your method has a parameter which itself accepts a TKey
, which sort of reverses the variance.
It's easiest to see why it's prohibited by imagining if it were allowed. Then you could write:
public class ObjectKeyedCache : ICache<object, object>
{
public object GetOrAdd(object key, Func<object, object> valueFactory)
{
// Let's ignore the specified key, and just pass in an object!
return valueFactory(new object());
}
}
Then you could write:
ICache<object, object> objectKeyedCache = new ObjectKeyedCache();
// Ah, due to contravariance, this should be okay...
ICache<string, object> stringKeyedCache = objectKeyedCache;
// Okay, this is a weird cache function, but bear with me
stringKeyedCache("key", text => text.Length);
That would then try to pass an object
reference into the Func<string, object>
created from the lambda expression text => text.Length
. Bang!
For even more details, read Part 5 of Eric Lippert's variance blog series. This is the most important part of that post:
Doing so makes my brain hurt, but this also builds character, so here we go!
Anything that makes Eric's brain hurt should probably be considered a major health hazard for the rest of it. You have been warned.
See more on this question at Stackoverflow