Invalid variance with delegate in interface method

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?

Jon Skeet
people
quotationmark

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.

people

See more on this question at Stackoverflow