Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why generic interfaces are not co/contravariant by default?

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.

like image 337
Konstantin Tarkus Avatar asked Aug 30 '10 19:08

Konstantin Tarkus


People also ask

How do you declare a generic interface as contravariant?

You can declare a generic type parameter contravariant by using the in keyword. The contravariant type can be used only as a type of method arguments and not as a return type of interface methods. The contravariant type can also be used for generic constraints.

What is co variance and Contra variance 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.

Can a generic be an interface?

Java Generic Classes and SubtypingWe can subtype a generic class or interface by extending or implementing it. The relationship between the type parameters of one class or interface and the type parameters of another are determined by the extends and implements clauses.

What is covariance and Contravariance in generics 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.


2 Answers

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.

like image 77
Jon Skeet Avatar answered Oct 22 '22 14:10

Jon Skeet


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?

like image 24
Darin Dimitrov Avatar answered Oct 22 '22 13:10

Darin Dimitrov