18
votes

For example IEnumerable<T> interface:

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

In this interface the generic type is used only as a return type of interface method and not used as a type of method arguments thus it can be covariant. Giving this, can't compiler theoretically infer the variance from the interface? If it can, why does C# requires us to set co/contravariance keywords explicitly.

Update: As Jon Skeet mentioned this question can be spited into sub-questions:

  1. Can compiler infer generic type's co/contravariance by how it is used inside current generic type and all it's base types?

    For example.. How many generic interface parameters from .NET Framework 4.0 can be marked co/contravariant automatically without any ambiguity? About 70%, 80%, 90% or 100%?

  2. If it can, should it apply co/contravariance to generic types by default? At least to those types which it is capable to analyze and infer co/contravariance from the type usage.

2
Excellent question. I anticipated your question in 2007 when we were designing this feature. Which is why I wrote an article answering it back then: blogs.msdn.com/b/ericlippert/archive/2007/10/29/…Eric Lippert
There are some excellent answers below regarding why this isn't automatic. I just wanted to add that ReSharper will suggest co/contra-variance and even do the refactoring for you if you accept its suggestions. I don't know how well it works in the ambiguous/difficult situations, though (I'm assuming it just won't attempt a suggestion in those cases).Stephen Cleary

2 Answers

16
votes

Well, there are two questions here. Firstly, could the compiler always do so? Secondly, should it (if it can)?

For the first question, I'll defer to Eric Lippert, who made this comment when I brought exactly this issue up in the 2nd edition of C# in Depth:

It's not clear to me that we reasonably could even if we wanted to. We can easily come up with situations that require expensive global analysis of all the interfaces in a program to work out the variances, and we can easily come up with situations where either it's <in T, out U> or <out T, in U> and no way to decide between them. With both bad performance and ambiguous cases it's an unlikely feature.

(I hope Eric doesn't mind me quoting this verbatim; he's previously been very good about sharing such insights, so I'm going by past form :)

On the other hand, I suspect there are still cases where it can be inferred with no ambiguity, so the second point is still relevant...

I don't think it should be automatic even where the compiler can unambiguously know that it's valid in just one way. While expanding an interface is always a breaking change to some extent, it's generally not if you're the only one implementing it. However, if people are relying on your interface to be variant, you may not be able to add methods to it without breaking clients... even if they're just callers, not implementers. The methods you add may change a previously-covariant interface to become invariant, at which point you break any callers who are trying to use it covariantly.

Basically, I think it's fine to require this to be explicit - it's a design decision you should be making consciously, rather than just accidentally ending up with covariance/contravariance without having thought about it.

5
votes

This article explains that there are situations that the compiler cannot infer and so it provides you with the explicit syntax:

interface IReadWriteBase<T>
{
    IReadWrite<T> ReadWrite();
}

interface IReadWrite<T> : IReadWriteBase<T>
{

}

What do you infer here in or out, both work?