views:

200

answers:

3

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.

+6  A: 

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.

Jon Skeet
If making generic types covariance/contravariance can lead to "breaking clients" then why did .NET creators replace their major generic interfaces to co/contravariant counterparts in .NET 4.0?
Koistya Navin
@Koistya: It's not that way round: it's the other way round. If it's been inferred to be covariant, but then you add a method which makes it *invariant*, then any clients which are using the covariance will be screwed. I'll edit to explain this.
Jon Skeet
@Koistya: Making invariant types into variant types is in fact a breaking change; we decided that the benefit of the change was worth the pain of the break. See my article on that: http://blogs.msdn.com/b/ericlippert/archive/2007/11/02/covariance-and-contravariance-in-c-part-nine-breaking-changes.aspx - However, Jon correctly notes that the kind of break he is talking about is that *editing an interface can change its legal variance annotations unexpectedly*. We would therefore like to make those annotations explicit, rather than deducing them.
Eric Lippert
@Jon: Re "I hope Eric doesn't mind..." - quote away my friend. I trust you to quote me in context. :-)
Eric Lippert
Can you think of any example where client relying on a generic interface's type to be invariant. If changing generic from invariant to variant is a breaking change what could be a percent of code which will be affected by this change? In other words how many developers from a million will complain about it? :)
Koistya Navin
+5  A: 

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?

Darin Dimitrov
Compiler could apply `out` keyword here by default to the base interface based on usage and the same for the derived interface based on parent T
Koistya Navin
No, both can. Try it :-)
Darin Dimitrov
Yep, you can set both manually but from compiler point of view, it could set T be covariant by default in your example. Yes or No?
Koistya Navin
Yes, but if the compiler took this decision and I wanted to use `in`, in this case we have a conflict which can be resolved only by being explicit.
Darin Dimitrov
If you want to specify `in` then compiler will just keep it as you specified without any attempt to infer this type and if you didn't specified `in`/`out` explicitly compiler will make it's own assumption.
Koistya Navin
I usually don't like the compiler to make assumptions on my behalf... if there's a possible ambiguity, the compiler should *never* make that kind of assumption. How would you know whether your interface is covariant or contravariant ?
Thomas Levesque
@Thomas: Indeed. What makes this worse is that if *other* interfaces inherit from this one or have this one in the formal parameters or return types of their methods, then the analysis of *them* changes depending on whether this one is covariant or contravariant. Making that choice one way or the other has implications that are not local.
Eric Lippert
+6  A: 

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:

http://blogs.msdn.com/b/ericlippert/archive/2007/10/29/covariance-and-contravariance-in-c-part-seven-why-do-we-need-a-syntax-at-all.aspx

Eric Lippert
Eric, good article, I am regretted that I didn't see it before. It is called "...Why Do We Need A Syntax At All?" It's clear to me that we do indeed need a syntax but.. why should we always set variance explicitly in generic types?
Koistya Navin
@Koistya: Because (1) the analysis is potentially expensive, (2) the analysis produces ambiguities which *cannot* be errors, (3) any warnings produced would almost certainly be either false positives or confusing and difficult to correct, (4) the vast majority of interface types are *not* going to be legal for variance, so the feature benefits very few people, (5) during development, minor edits to an interface can change the inferred variance, making entirely other parts of the program behave differently; non-local effects are hard to debug, and (6) we have better ways to spend our budget.
Eric Lippert
And (7) it is confusing to have an *optional* syntax for something. It's bad enough that "private" is optional. Making subtleties about the conversion rules silently inferred for you is just asking for confusion; I don't want to make a feature that increases support costs.
Eric Lippert
Eric, That makes perfect sense! Thanks for your explanation.
Koistya Navin