Covariance and Contravariance in C#, Part Eight: Syntax Options


As I discussed last time, were we to introduce interface and delegate variance in a hypothetical future version of C# we would need a syntax for it. Here are some possibilities that immediately come to mind.

Option 1:

interface IFoo<+T, -U> { T Foo(U u); }

The CLR uses the convention I have been using so far in this series of “+ means covariant, means contravariant”. Though this does have some mnemonic value (because + means “is compatible with a bigger type”), most people (including members of the C# design committee!) have a hard time remembering exactly which is which.

This convention is also used by the Scala programming language.

Option 2:

interface IFoo<T:*, *:U> { …

This more graphically indicates “something which is extended by T” and “something which extends U”.  This is similar to Java’s “wildcard types”, where they say “? extends U” or “? super T”.

Though this isn’t terrible, I think it’s a bit of a conflation of the notions of extension and assignment compatibility. I do not want to imply that IEnumerable<Animal> is a base of IEnumerable<Giraffe>, even if Animal is a base of Giraffe. Rather, I want to say that IEnumerable<Giraffe> is convertible to IEnumerable<Animal>, or assignment compatible, or some such thing. I don’t want to conceptually overwork the inheritance mechanism. It’s bad enough IMO that we conflate base classes with base interfaces.

Option 3:

interface IFoo<T, U> where T: covariant, U: contravariant { …

Again, not too bad. The danger here is similar to that of the plus and minus: that no one remembers what “contravariant” and “covariant” mean. This has the benefit at least that you can do a web search on the keywords and get a reasonable explanation.

Option 4:

interface IFoo<[Covariant] T, [Contravariant] U>  { …

Similar to option 3.

Option 5:

interface IFoo<out T, in U> { …

We are taking a different tack with this syntax. In all the options so far we have been describing how the user of the interface may treat the interface with respect to the type system rules for implicit conversions – that is, what are the legal variances on the type parameters. Here we are instead describing this in the language of how the implementer of the interface intends to use the type parameters.

I like this one a lot; the down side of this is of course that, as I described a few posts ago, you end up with situations like

delegate void Meta<out T>(Action<T> action);

where the “out” T is clearly used in an input position.

Option 6:

Do something else I haven’t thought of. Anyone who has bright ideas, please leave comments.

Next time: what problems are introduced by adding this kind of variance?

Comments (66)

  1. mstrobel says:

    I vote for Option 1.  I remember seeing it mentioned some time ago in some other blog entry discussing the CLR support for variance, and I thought it was a very sensible notation.  Also, I happen to be one of those people who often has to stop to think about which is which when it comes to covariance and contravariance, so I can personally vouch that Option 1 is the most intuitive (at least to me :)).

  2. Peter Ritchie says:

    Does option 4 imply the generic parameters would be attributed?

    If not, how feasible is it to use attributes for co-/contra-variance?  For example:

    [Contravariant(parameter="T")]

    delegate void Meta<T>(Action<T> action)

    This has a couple of advantages, it’s binary compatible with .NET 2.0 and the user is free to make an alias for ContravariantAttribute…

  3. Mike Thomas says:

    #3 and #4 are very nice because they are explicit – #3 esp. fits in with existing c# syntax.  maybe even something like: interface IFoo<T, U> where T is covariant, U is contravariant { …

    However like you said, the danger is that then everyone needs to learn what "covariant" and "contravariant" are – I think that is where option #2 – the Javaesque has the advantage…

  4. Jason Bock says:

    I definitely do NOT like 5 – out is already a keyword and this would just lead to more confusion.

    Honestly, I don’t have a good feeling for the others. I guess I would go with #1, with #3 being close behind. #4…did you mean attributes, or not? That’s a bit confusing.

  5. Matt says:

    After my previous suggestion I am inclined towards #3. The verbosity is of a correct level correct, the names are googleable (don’t underestimate the utility of this, especially when looking for code examples on the web) and actually correspond to the exact mechanism in use.

    Obviously the slight confusion is that they are no longer constraints they are freedoms 🙂

    There is the danger of more and more being expressed in the constraints/freedoms (especially if you add static operator constraints 🙂 but I think it’s cleaner than my initial suggestion so gets my strong vote.

    I am strongly -100 points out the door on any use of punctuation within general purpose modern languages languages*  (I preferred extends to : to be honest).

    The existing ; : ? at least all hail from C/C++ and thus get some serious positive points based on familiarity. This doesn’t need a terse syntax (unlike the shortcuts for nullable type useage) and therefore shouldn’t be used.

    Just my opinion of course 🙂

    *  strong assumption that a modern IDE is present for code completion. I know not everyone does but when a significant proportion do its reasonable to target to that population

  6. Rob says:

    I like option 1.  Though it may be difficult for others to remember what the + and – mean, there is precedent for this usage in other languages and notations.  Beyond Scala, I’m pretty sure OCaml uses this too.

    People are going to have to learn something new anyways.  It might as well be the common notation.

  7. Eric Lippert says:

    I agree with you about punctuation. I would prefer "extends" (for base classes) and "implements" (for interfaces) to the semantically ambiguous ":".

  8. Luke says:

    So far:

    delegate R Func<-A, +R> (A a)

    delegate R Func<*:A, R:*> (A a)

    I suggest:

    delegate R Func<* is A, R is *> (A a)

    Let me use your example from an earlier post:

    Func<Animal, Giraffe> f1 = whatever;

    Func<Mammal, Mammal> f2 = f1;

    Plugging in types, Func<-A, +R> would become:

    Func<* is Animal, Giraffe is *>

    To see if Func<Mammal, Mammal> would be valid:

    Func<Mammal is Animal, Giraffe is Mammal>

    Yay, those "is" statements would equate to true!  Plus, one uses "is" to test assignability, which is exactly what this whole covariance and contravariance thing is about.  Now, let’s test this against intuitiveness.  The programmer needs to know that when you have:

    delegate TypeA = delegate TypeB

    that the type parameters of TypeB get plugged into the type parameter names in the declaration (A & R in my example) and the type parameters of TypeB get plugged into the wildcards.  He/she would need to understand that you take what you know must be true (TypeB), plug those types into the type parameter names, and plug what you want to test (can TypeA hold TypeB?) into the wildcards.  Another way to say this is that A & R need to exist before * can exist.  

    A benefit of "is": it allows for "equals" and not just bigger than/smaller than, implements/extends, or A : B.

  9. Eric Lippert says:

    That’s a great idea!

  10. Matt says:

    Yeah – Luke’s idea certainly gets several positives to outweigh the -100. especially since * meaning wildcard is reasonably ingrained (and relatively platform agnostic)  thanks to globing.

    Gets my reflex vote but I haven’t given it much detailed  thought yet

  11. How about combining Luke’s idea with option 3, ie:

    Func<T, U> where T is Animal, Mammal is U { …

  12. apenwarr says:

    The danger of using the ‘*’ wildcard is that the type definition in a generic is *already* a wildcard, to some extent.  Sure, it’s a different kind of wildcard… but do you really want to be explaining to newbies about different kinds of type wildcards?  Ouch.

    Suggestion #5 gets points in this respect, but only a few: it’s still not obvious when I should say "in T" vs. "T" unless I think for a very long time.

    To be honest, a lot of this covariance/contravariance stuff seems too complex.  People complain about extension methods and iterators and LINQ, but those all seems very simple to me once you’ve read an introduction about them.  But covariance/contravariance never gets any easier, and that should be a big warning sign.  Why not just make it really easy to create an adaptor instead?  (For example, C++ doesn’t try to grapple with fancy covariance/contravariance issues, but it does have boost::bind, which is absolutely great to use and very easy to understand – at least in semantics.)

  13. That should be

    interface IFoo<T, U> where T is Animal, Mammal is U { …

    of course.

  14. Funny how everyone else hates 5. I love it, I think it’s the only option that makes the slightest bit of sense.

    The thing I find frustrating about this talk of variance is that it doesn’t particularly help in the case I want variance for, though.

    I want to be able to declare a variable of type List<?> and then populate it by calling a method by reflection that I know returns a List<T> but I don’t know what T is. And I want to be able to do that *without* having to have anything special on the List class to accomodate it – because I didn’t write the List class in the first place. And if I have constraints on T I want to be able to express them too.

    Then I want to be able to access T-typed properties on that variable but just get them back as type "object" (or the appropriate constraining type).

    It shouldn’t be necessary to still be using the non-generic IList and IEnumerable types all over the place just because we don’t statically know the relevant "T". And when I define a generic interface, or class, I shouldn’t have to jump through hoops to define non-generic versions of everything I make if I might ever want to use it on a reflection-instantiated class.

  15. I seriously need to read and think before I press submit. It would have to be something like:

    interface IFoo<T, U> where T is *, * is U { T Foo(U u); }

  16. Luke’s idea seems very compelling but I still find it hard to wrap my head around. It seems like it ought to make the Action / Meta thing easier to follow but it really doesn’t, to me.

    I have a hard time following what Meta might actually do so here’s a concrete example – using EventHandler<T> instead of Action, then an AddHandler method seems like it would have approximately the same signature as Meta. Am I following that right?

    delegate void EventHandler<* is TEventArgs>(object sender, TEventArgs e) where TEventArgs : EventArgs;

    delegate void AddHandlerMethod<TEventArgs is *>(EventHandler<TEventArgs> handler) where TEventArgs : EventArgs;

    Nope, I’m still lost. What does that mean I can actually do with such an EventHandler compared to if it was declared as a regular EventHandler? What can I do with AddHandlerMethod?

    I still feel like this kind of declaration-site variance is terribly unuseful compared to Java’s use-site variance, which *does* make sense to me (and offers a solution to the scenario in my prior comment, at least sort of…)

  17. Luke says:

    apenwarr, I do not think newbies will be exposed to covariance and contravariance [knowingly].  This will be useful to people who like to take the language to the limits and people who write frameworks.  Maybe I’m wrong, but I think support for covariance and contravariance will show itself by things working more naturally, because people will use code that allows for variance more often than write it.

    Eric, I don’t recall seeing any real-life, motivating examples for this where such support would make code significantly more readable (or somewhat more readable in many places).  In other words, what scores points against the -100?  If this is coming up in a future post, ignore this question. 🙂

  18. Joe says:

    With the "is" keyword, would we really even need the "*"? To me it seems like unnecessary typing, and I’m a hprrbile tpyer so the less typing the better. In my eyes, "delegate R Func<is A, R is> (A a)" conveys just as much information as "delegate R Func<* is A, R is *> (A a)"

    But so far, I like that general idea the best, asterisk or not.

  19. brandf says:

    My previous comment didn’t seem to go though, so forgive me if this ends up getting posted twice.

    Option 3 should be :

    interface IFoo<T, U>

     where T: covariant

     [where] U: contravariant { …

    to be consistant with existing constraints.  Commas only seperate constraints on a single parameter.

    I would also like to suggest you avoid confusion between generic constraints and ‘freedoms’, using a ‘let’ clause.

    interface IFoo<T, U>

     where T: IBar  //a constraint on T

     let T: covariant //a ‘freedom on T

     where U: new() //a constraint on U

     let U: contravariant  //a freedom on U

    { …

    basically, seperate out constraints (where) from freedoms (let) in a LINQ like declaritive way.  This is better IMO than putting anything in between <>’s because it fits more with the current constraint model (just where clauses)

    -Brandon

  20. Bradley says:

    I think the description of U in Option 2 is reversed. The post says that "*:U" means “something which U extends”. Shouldn’t that be “something which extends U”? The description of "T:*" could also be simplified to “something which T extends”.

  21. Bryan Watts says:

    Freedoms are certainly the most palatable solution. Whether ‘covariant’ and ‘contravariant’ are the most intuitive keywords is debatable.

    How about:

    interface IFoo<T, U>

     where T : IBar

     let * : T

     where U : IStuff

     let U : *

    { …

    The relatively universal wildcard ‘*’ still denotes ‘any type where’ and the freedoms provide a clean, extensible syntax.

    My only reservation is using ‘:’ to denote a non-inheritance relationship. How about this even:

    nterface IFoo<T, U>

     where T : IBar

     let * >= T

     where U : IStuff

     let U <= *

    { …

  22. silky says:

    my vote is for lukes idea for readability; it’s great.

    a secondary vote for the attribute approach due to backwards compat and further ‘verbose-ness’. it does allow searching for the concepts which is great.

    but out of all; i prefer lukes. great idea. it actually helped me understand the concept incredibly easily.

  23. Bryan Watts says:

    I am wavering on the "constraint/freedom" question.

    Saying ‘U may be anything like this’ is equivalent to saying ‘U cannot be anything out of this range’, so I would still consider variance a constraint, not a freedom.

    From that perspective, I like "assignment constraints":

    where T : IBar

    where T <= *

    where U : IStuff

    where U >= *

    Check for a maximum of 1 inheritance constraint and 1 assignment constraint.

  24. Brian says:

    Option 1:

    interface IFoo<+T, -U> { T Foo(U u); }

    I am most accustomed to this notation from the CLR.

    It’s well attested and works.

    This immediately gets my vote.

    Option 2:

    interface IFoo<T:*, *:U> { …

    The first thing that comes to my mind is some kind of strange pointer…

    Not very intuitive.

    Option 3:

    interface IFoo<T, U> where T: covariant, U: contravariant { …

    Not bad, but really no strong points over option 1.

    Also, IMO, it is too verbose.

    Option 4:

    interface IFoo<[Covariant] T, [Contravariant] U>  { …

    I can not really explain why, but I do not want to think of variance as an attribute of a something.

    Option 5:

    interface IFoo<out T, in U> { …

    I have a handle on + and -, but I have a hard time digesting in/out.

    Options 6 (Luke)

    delegate R Func<* is A, R is *> (A a)

    I like the is keyword being used here. However, I am opposed to the * notation. Again, it looks like some kind of weird pointer. Can it be done without the *? And if so, how? Not that I have a better suggestion.

    Weighing all of the pros and cons, I prefer option 1.

  25. Slightly crazy syntax suggestion:

    delegate R Func<A, R>(A a)

     using A as params

     using R as return;

    It has the same problem as the "in/out" proposal though.

    I’ve been trying to articulate my problem with this compared to Java-style use site variance and I think I’ve figured it out. With this system, interfaces are only variant in a fairly limited set of scenarios: IEnumerable and IEnumerator can be made variant but IList never can. And the circumstances in which a *class* could be variant are so few as to be almost not worth considering. It’s illuminating that Eric hasn’t even mentioned the possibility of variance on classes, while strongly hinting that variance on delegates and interfaces is likely.

    But in fact it’s not the type, per se, that is variant; it’s the particular *use* of it. For instance:

    public void printAll(List<*> list) {

     foreach (var obj in list) Console.WriteLine(obj.ToString());

    }

    List<T> is not and cannot be covariant, but the list parameter of the printAll method *is* covariant, because it’s being *used* covariantly. (The * in this case is essentially shorthand for "* is object" since everything "is object").

    On the other hand:

    public void populateList(List<String is *> list) {

     list.Add("Hello");

     list.Add("World");

    }

    List<> isn’t contravariant either, but in this case it’s being used contravariantly.

    C#’s historical way of handling this has been to make all the *s into explicit type parameters, so we’d have something like

    public void printAll<T>(List<T> list) {…}

    and

    public void populateList<T>(List<T> list) where String : T {…}

    (actually I’m not completely sure that second one is really even legal) and then hope that we can hide the complexity from end users by always inferring the T. This fails in all sorts of ways, ranging from method overloading (if there was a printAll or a populateList that *didn’t* have a generic parameter, C# seems to fail to do anything useful or intuitive with inference) to the scenario I mentioned where T is a type instantiated by reflection and completely unknown at compile time. Or another example – T is an internal type only exposed by a public interface (and yes, I know using List<> here would be bad in real code, but it’s the best-known generic type out there, the same situations occur with homegrown generic classes):

    public interface IFrobbable { void Frob(); }

    internal class VeryFrobbable : IFrobbable { … }

    public class FrobbableFactory {

     private List<VeryFrobbable> frobbables;

     public static List<* is IFrobbable> GetFrobbables() {

        initialize();

        return frobbables;

     }

    }

    Client code:

    public void FrobEveryOther() {

     var frobbables = FrobbableFactory.GetFrobbables();

     for (int i = 0; i < frobbables.Count; i += 2) {

       frobbables[i].Frob();

     }

    }

    The declaration-site variance that you’re talking about seems to allow a very limited number of scenarios and depends incredibly heavily on the provider of all your interfaces creating variant versions of every interface. For example, to make anything like the frobbable scenario work using what you’re proposing, you need:

    interface IReadList<* is T> {…}

    interface IWriteList<T is *> {…}

    interface IList<T> : IReadList<T>, IWriteList<T> {…}

    Is it really realistic to assume that everyone who defines an interface will think to split it up that way? Or that nobody will want to use an interface whose author *didn’t* think of this, in a variant way? The situation gets even worse for classes because you need to create three interfaces *plus* the class. And the kicker? That only works for classes with one type parameter. IDictionary<K,V>? Fuhgeddaboudit. Combinatoric growth of number of interfaces needed to cover everything that a user might want to do.

  26. Last comment of the night, I promise. But I just want to emphasize how important I feel this is. I’ve been begging for variance in generics for a very long time and very strenuously. But if all you’re going to do is the delegate/interface variance you’ve been talking about, no matter how intuitive you manage to make the syntax, I’d actually argue strongly *against* bothering to include it at all. It adds a lot of complexity and helps in pretty much zero of the scenarios where I’ve encountered a need for variance in real life. If you can’t offer use-site variance, just stick to covariant return types on overridden methods, and beyond that, don’t even bother.

  27. Allan says:

    The following Microsoft Research is an interesting read:

    "Variance and Generalized Constraints for C# Generics"

    http://research.microsoft.com/research/pubs/view.aspx?type=inproceedings&id=1215

    And it discusses variant classes.

    While it would be useful, at least variant interfaces would be a great start which hopefully could be extended in the future.

  28. Marc Brooks says:

    How about using base, it already has some meaning relevant to this:

    delegate R Func<base A, R base> (A a)

    So the position of base indicates the co/contra

  29. Contra says:

    Marc,

    All your base are belong to us.

  30. Stefan Wenig says:

    Option 1 (+/-) looks good enough for me. minus by minus equals plus in general math too, this is a benefit of this syntax.

    Option 2 (T:*/*:U) confuses me. Like Bradley, I thought it to be reversed, and I still don’t understand how *:U (or ? extends U, for that matter) expresses "something which U extends". It’s also hard to relate "something which U extends" to "interface is contravariant on U"…

    Option 3 (where "constraints"): I share the reservations about using where for constraints AND variance.

    Option 4 (Attributes): good enough. + and – might be easier to relate to bigger/smaller types, but co/contravariance are easier to google. tough to decide, i suppose either one will do. I’d perfer a contextual keyword to attributes though. this is a first class language feature after all!

    Option 5 (in/out): I see how you like this for simple cases, but get real: it’s incorrect for others, and at the end of the day, you’re not going to include incorrect syntax into the language, even if we’d all love it. (which I do not, for exactly this reason)

    Luke’s suggestion (* is A) and Matt’s one (positional "base"): I find those just as confusing as option 2.

    Rasmus’s suggestions indicates that he didn’t understand the problem, the "where" syntax led him to believe that we’re talking about constraints (or maybe I’m not getting it). Goes to prove that option 3 is a bad idea.

    Actually I like Peter Ritchie’s suggestion a lot, only that I would recommend built-in syntax:

    interface IFoo<T,U>

    covariant on T

    contravariant on U

    having this on the interface rather than on the type parameters themselves indicates that we’re talking about a quality of the interface, not of the type parameter. all other options except #5 (especially #3) seem to lead people to think more about what the type parameter is, when variance actually says nothing about that type parameter, but only about the generic interface’s (or delegate’s) relation to that parameter.

    The example above reads quite intuitively. I have an interface with two parameters, they can be whatever the user likes (unless there are additional where constraints). the interface is covariant on T and contravariant on U. covariant means that assignability of IFoo goes with T, and contravariant goes into the other direction. this is not hard to remember, "co" and "contra" express this well. The only thing that needs to be remembered (or logically deduced, which is possible) is that covariance is typically useful for output parameters, while contravariance is typically useful for input parameters. since this is only the typical case, and wrong in others (Meta<+T>) I don’t see how you could make this mapping more implicit. My opinion is that for the sake of correctness, we have to accept this difficulty.

  31. Bert Huijben says:

    How about re-using the existing c# syntax and allow re-ordering the parameters

    interface IFoo<T, U>

      where T: Mammal

      where Mammal : U

    {

    }

  32. Eric Lippert says:

    >  I still don’t understand how *:U expresses “something which U extends”.

    You and Bradley are right, that is badly worded. I intended to say “something which extends U”. I’ll fix it.

  33. Eric Lippert says:

    Stuart: I want to be able to declare a variable of type List<?>

    Indeed, when we were designing anonymous types we considered doing that kind of inference. We’re calling those "mumble types", as in "I have a list of… mumble mumble mumble".

    Obviously we didn’t end up doing them for C# 3.0 but it does seem like a generally useful addition to the type system, so we’ll consider it for hypothetical future versions.

  34. Aaron says:

    I’m going to cast my vote for Luke’s "is * / * is" idea.  In present-day C# where we don’t have cov/cnv, I would have to test and cast the interface manually, and that is precisely the test I’d be using.  It seems natural to put it in the definition instead.

    I strongly dislike the overly verbose versions (#3 and #4).  I thought that the reason we used C# instead of VB is that we don’t particularly like the verbosity.  Plus, even though I can remember what covariant and contravariant mean on a basic level, I’m never going to remember the exact effect each one has within the language.

    I agree that #2 is conflating two very different concepts, but they’re already conflated in Generics in exactly this way.  If I declare IFoo<T> where T : IBar, and I have a class MyClass which implements IBar, I can legally use an IFoo<MyClass>.  Furthermore, this doesn’t bother me and I don’t find it the slightest bit confusing; it mirrors the real inherits/implements syntax.

    #1 is okay.  It conveys a bit more information about what’s actually happening than the verbose versions, but I still know I’m going to screw it up eventually if I have to contend with it.  And if I were to hand the code off to an unfamiliar programmer, he might very well look at it and go, "huh"?

    #5 bugs me because in/out are already reserved words that mean something totally different.  It does make sense, but please, no more overloaded keywords.

    Also, I don’t think any of these suggestions truly address the Meta issue.  It’s a conceptual problem, a double-negative in a sense.  I think if people really want to program like this (I can’t imagine why), then they probably already know that they’re playing with fire.  It’s the rest of us, who might have no idea how dangerous it is, that you should be worrying about. 🙂

  35. Eric, that’s fantastic news that you’re considering "mumble types" for a future version. I really hope it’s a sooner rather than later future version – in particular I hope it ends up being at least the same version as the changes you’re discussing now. I think that introducing both concepts together would help to come up with a coherent syntax between the two. And I’ll shut up now about pushing for the feature because you know how I feel about it, and go back to discussing what we’re talking about now.

    I’m trying to think of ways to express what Foo<* is T> actually means in english to see if there’s a better way to express it in the language.

    What it really means is "Foo<T1> is Foo<T2> if T1 is T2". I find it really hard to read that meaning into *any* of the proposals (except the "in/out" one which everyone hates). Similarly contravariance means "Foo<T1> is Foo<T2> if T2 is T1"

    So how about this as an actual syntax proposal:

    public interface IEnumerable<T>

       where IEnumerable<this> is IEnumerable<base> {

     …

    }

    public delegate Action<T>(T t)

       where Action<base> is Action<this>;

    When there’s multiple type parameters it becomes a little less obvious. Some possible approaches:

    public delegate R Func<R, A>(A a)

       where Func<this, base> is Func<base, this>;

    (problem of how to express a parameter that isn’t variant at all – "this" on both sides?)

    public delegate R Func<R, A>(A a)

       where Func<this, *> is Func<base, *>

       where Func<*, base> is Func<*, this>;

    (which I think I like better except that it still has the confusion of people expecting * to mean "pointer" rather than "wildcard". There are other symbols that could be used but I can’t think of any actual words that carry the right meaning…)

  36. Ooh, or:

    public delegate R Func<R, A>(A a)

       where Func<this, A> is Func<base, A>

       where Func<R, base> is Func<R, this>;

    Yes, it’s verbose, and probably not terribly easy to write correctly (although I suspect the IDE could help a LOT with this kind of clause) but code is read much more often than it’s written. And having the code written this way gives some kind of intuitive sense of what it means.

    Plus it doesn’t have the meta problem:

    public delegate Action<T>(T t)

       where Action<base> is Action<this>;

    public delegate Meta<T>(Action<T> action)

       where Meta<this> is Meta<base>;

    It accurately expresses what’s actually going on there without presuming the meaning as input or output.

  37. Stefan Wenig says:

    just a thought experiment:

    instead of

    interface IFoo<T:*, *:U>

    we could write

    interface IFoo<T, U> : IFoo<T:*, *:U>

    or, using Luke’s syntax,

    interface IFoo<T, U> : IFoo<T is *, * is U>

    this is not beautiful, but it gives a better hint as to what it really implies for the interface.

    now ":" goes to mean "assignable from" in addition to "implements" and "extends" (which is worse, because the latter two were easily identifiable due to the I-prefix for interfaces). so no actual syntax recommendation, but just a way that makes it more understandable for me, and might inspire other ideas.

    we could take Luke’s way even further:

    interface IFoo<T, U> is IFoo<T is *, * is U>

    even more explicit:

    interface IFoo<T, U>

     is IFoo<A, B>

       where T is A

       where B is U

    ugly, I know.

    plus, I’m still unhappy with the positional thing. Makes me think more than it should. end of thought experiment.

    Thinking about in/out, I believe automatic variance would not be a bad thing in every case. I know, I voted against it even before you explained it, but think about this: when is any of the problems really present in case of delegates? I believe automatic co/contravariance on delegates is both easier to resolve and less likely to introduce breaking changes. also, we could use a single keyword/attribute to indicate that we want co/contravariance, and let the compiler detect which one.

    here’s how I got there. The Meta<> Problem

    delegate void Action<in T> (T arg);

    delegate void Meta<out T>(Action<T> action);

    If we would be able that we want to use Action<T> as the input parameter, not T, the compiler could figure it out that T needs to be covariant, because Action<T> is already contravariant on T. So we would need some way indicate that "input" refers to Action<T>, not just T. The easiest thing would be to indicate that with the action parameter, but there it is already clear that it is an input parameter. So why bother?

    It’s harder for interfaces though. They are more complex, and while it’s quite clear that adding/modifying parameters in a delegate would change assignment semantics, it’s more unexpected in interfaces (and essentially impossible to control, who thinks about variance whenever changing or adding interface members?). Plus, there’s the exponential growth problem. Can we come up with something better?

    Let’s take an example in +/- syntax:

    interface IDoer<-T> {

     void Do(T obj);

    }

    interface IDoable<+T> {

     void ApplyDoer (IDoer<T> doer);

    }

    now, we obviously would like to write IDoer<in T>, but absolutely not IDoable<out T>. Now imagine this in the syntax I recommended above:

    interface IDoer<T>

     contravariant on T

    interface IDoable<T>

     covariant on T

    if we replace contravariant with "in" and covariant with "out", we end up having the same problem. but what if, instead of "covariant on T", we could just say "contravariant on IDoer<T>"? Just like with implicit delegate co/contravariance, the compiler could easily figure out that in order for IDoer<T> to be contravariant, T needs to be covariant.

    interface IDoer<T>

     variance: in T

    interface IDoable

     variance: in IDoer<T>

    now the wording sucks. we need better keywords/syntax. but besides that, I really love this. we have in/out semantics, it is correct, and it is easily resolvable by the compiler, because it doesn’t need to look at the entire interface.

    an additional problem is that documentation generation tools like ndoc could not tell that the user wrote "in IDoer<T>" instead of "out T". this is essential for documentation, so the compiler would somehow have to annotate this.

  38. Stefan Wenig says:

    another thought: specifying variance for interfaces is really just a promise to not use T in any other way than specified (input/output). once this has been made clear, there’s simply no reason why the compiler would not assume co/contravariance as needed, but the programmer would have to comply with that statement, which the compiler could check.

    you’re not going to keep design by contract away from us forever. there’s even fascinating research in MS research (spec#). how do those fit together? if variance can be derived from an explicit constraint (contract), you might want to use similar syntax, or at least align them in some way. then again, it’s probably to early to know how C#’s hypothetical future DBC syntax is going to look like. still, this could be a hint when trying to cook up a syntax for what I’ve proposed above.

    (note that "variance: in IDoer<T>" above also implies that T is not used directly by this interface, only via IDoer<T>. this would probably have to be made explicit when using a constraint syntax.)

  39. mstrobel says:

    Well, since Stefan already opened the door, I love Spec#, and I want contracts in C# :).

  40. Stefan Wenig says:

    that’s right, mike. DBC would have a greater impact on the way we code than any co/contravariance, however fascinating this is. (even without the amazing static analysis spec# does)

    (while the door is open, can we please automatically generate unit tests from those contracts too?)

    but then I want memberof (ldtoken for members), generics and lambdas in attributes, I want Expression<Func<T>> parameterized (lambdas) so that the expression tree does not have to be parsed and processed for every call (I’ll just say i4o, the indexed variant of LINQ 2 objects), and … – no, wait, let’s stay focused for a minute, OK? 😉

    (ok, I can’t hold it back. pattern matching. now I’ve said it. but before we get into macros and meta-programming, let’s get back to that covariance syntax problem of that hypothetical… hey, can we give it an imaginary version number, like C# 4i? or does that sound too much like oracle?)

  41. Kerneltrap says:

    interface IFoo<T, U> where T is Animal, Mammal is U

    looks very intuitive – no need to remember anything.

  42. In my opinion it is important to have a clear verbose format. This is something quite complicated that should not be hidden away in a small added + or -.

    I am just brainstorming here, but how about this (introducing a new keyword "assignable to"):

    interface IFoo<T,U>

     assignable to IFoo<R, S> where R is T where U is S;

    {

     …

    }

    This has the advantage of making it extremely clear for the user of the interface what flexibility the co-/contravariance gives him, at the cost of making it unclear for the implementor which restrictions this imposes on him.

    Since interface-users should clearly outnumber implementors – especially for the types of interface where co- and contravariance is an issue – this should be an ok tradeoff.

  43. I’m very much against a very verbose syntax.  In my experience though symbols are initially confusing, you get used to them and they’re much easier to skim (since they’re easier to ignore).  Frequently, you’ll be perfectly happy to ignore covariance and contravariance, so that’s a plus.

    Consider also the competition which C# faces from dynamic languages.  These are often preferable precisely because they contain less "superfluous" non-behavioural syntax.  Succintly; they just say what needs to be done, nothing more.

    I’m also hoping that a good number of other "restraints" might make it into the language.  Currently, it’s not possible to demand a type have a  particular static member (except the parameterless constructor).  That capability would be immensely useful almost instantly for easy requirement of things like operators, to implementations of wrapping types which add functionality such as .Equals based on .CompareTo or whatnot.

    What core problem does covariance and contravariance solve?

    It allows more generic code.  It reduces the problem of "leaky abstractions".  Basically, it improves encapsulation.  But it’s not a big win.

    For example, the ability to implement an interface as defined by a class (i.e. to _not_ inherit it’s implementation optionally) might be a far greater win.  Or the ability to "add" features to a class. Or the ability to "uncurry" an instance method into a static method.

    A lot of code I encounter isn’t properly structured because it’s too much work to do so in C#.  If I have a custom list which implements, say, a priority queue, then implementing that as a class (with all the appropriate interfaces, including IEnumerable, IEnumerable<T>, ICollection, etc…) is a lot of work, so instead, everyone generally opts for the less encapsulated, more spagetti-code but easier option:  just implement the key feaures of a priority queue internally and don’t abstract it out, don’t allow code reuse, and leave the data structure implicit, not explicit.

    And not just "framework" creators need these kind of abilities (the ability to easily generate a class or instance with a particular set of "interfaces" or behaviours).  Many real-world frameworks require the "user" of the framework to provide objects which fit certain interfaces and conventions.  This pattern is sometimes called "Inversion of Control" or "Dependancy Injection" – but what it means is that:

    Everyone needs to be able to flexibly, easily, and comprehensibly be able to implement interfaces and other functionality contracts.

    Improving Covariance/Contravariance in C# should not imply that doing so becomes more complex, since that would defeat the purpose of language improvement.

  44. Eamon, I don’t disagree with a lot of your argument, but I don’t think it applies to this particular scenario.

    The basic principle is "simple things should be simple, and complicated things should be possible". The fact that it doesn’t say "complicated things should be simple" is *deliberate* – trying to achieve that always inherently hurts the simple things.

    I agree that it should be a lot simpler than it is to implement ICollection<T>, and for that matter IList<T> and IDictionary<K,V> (although I’m not sure how much easier it could be to implement IEnumerable than it is today with "yield" – that’s already practically trivial). These are simple things and shouldn’t be so damn hard.

    But covariance and contravariance are not simple things. They’re advanced features for advanced framework designers. I can’t think of a single use of this kind of variance outside of a framework. (The use-site "mumble type" variance could be used by everyone, which is one of the reasons I like it better overall). That means that (a) writing variant interfaces and delegates will be rare, and done by technically advanced users, (b) USING variant interfaces and delegates will be common by people who AREN’T so proficient, and (c) the less proficient people will only occasionally encounter variant types, so they won’t have any opportunity to "get used to" the feature.

    All of that means that it’s ok to write a little bit more (and we’re only talking about one extra line of code per variant type parameter, here, even in the more verbose suggestions such as mine) when you’re implementing the type, because the person doing that NEEDS to know what they’re doing, but it’s very important that someone READING the code be able to understand what it means without being an expert. One of the reasons C# is so much more friendly than C++ is that there aren’t miscellaneous punctuation symbols all over the place that radically change the meaning of your code.

    Even the comments here show that the meaning of this stuff isn’t at all intuitive. Eric has spent eight blog posts explaining variance in detail, along with various syntax suggestions, and yet Kerneltrap is making a syntax suggestion that demonstrates a complete lack of grasp of what variance actually means. (Not picking on you, Kerneltrap, it’s NOT obvious and it IS hard to get your head around. Hence why it’s so important to have a syntax that helps rather than being more obscure)

  45. Stefan Wenig says:

    I agree to the notion that in this case, verbosity hurts less than perl-like obscurity. rasmus’ notation is similar to my last proposed "is" notation (just replaces "is" with "assignable to"). i don’t think "assignable to" is bad, but I do think that "is" is clear enough in this case, no need for another keyword (would avoid the untypical two-word keyword too). but that’s just me.

    still, of the two syntaxes, i like the first one better:

    interface IFoo<T, U> is IFoo<T is *, * is U>

    (instead of crossing out explicit type parameter names, which is maybe easier to read if you do it the very first time and have to spend minutes reading it anyway, but harder if you do it on a regular basis. I posted the crossed-out syntax merely as a trigger for other people’s ideas, but I don’t like it as it is).

    disclaimer: i don’t think that verbosity works for delegates, where variance will arguably be more common, and the overhead of verbose syntax would probably double a typical delegate declaration. i still favor automatic co/contravariance for delegates, or alternatively a single keyword that indicates that I want it, but leaves the details to the compiler.

    Eric: would variance be limited to interfaces, or could we have it for generic classes too? (after all, delegates are just classes anyway)

  46. interface IFoo<T, U, V> is IFoo<base(T), U, derived(V)>

  47. Another variant on the notation I proposed earlier:

    delegate R Func<R, A>(A a)

     where Func<R, base(A)> is Func<base(R), A>;

    or

    delegate R Func<R, A>(A a)

     where Func<R, A> is Func<base(R), A>

     where Func<R, base(A)> is Func<R, A>;

  48. Stefan Wenig says:

    stuart:

    but this re-introduces the double-meaning of where (contraints/variance), plus I don’t think that base() and derived() make it any clearer who is the base and who derives than the T:* notation

    I’m still not entirely happy with the "is" constraint syntax. it’s clear, but is it really useful to have a syntax that spells out the meaning of variance? I don’t have a problem with the verbosity, but for me the "covariant on" and "contravariant on" clauses on the interface (not on the type arguments) would be the most intuitive ones when I’m working with it. I have to grasp co/contravariance once, the only notation that takes this away is the in/out notation, which is either wrong (Erics option #5) or overly verbose (my proposal of applying the in/out constraints not only to T, but maybe also to IDoer<T> etc.). plus, while in/out syntax is easier to write and understand, it makes it much harder to actually understand what assignment compatibility it really achieves.

    I also ask everyone to challenge my assumption that automatic variance is less of a problem for delegates than for interfaces. the more I think about it, I believe that it would be a Good Thing, probably even as opt-out (on by default). Am I wrong here?

    to summarize, for interfaces I’d prefer

    interface IFoo<T,U>

       covariant on T

       contravariant on U

    clear in what it does (google-able keywords) to which party (interface, not type parameters). once you grasped, easy to understand which assignment results from it (co: same as type-parameter, contra: opposite)

    for delegates I’d prefer automatic co/contravariance, maybe with the possibility to opt out ("invariant on T")

    everything else I’ve posted are mere thoughts that I would not like to see in the language unless somebody comes up with better variations.

  49. Stefan, I don’t need to challenge your proposal of automatic variance on delegates, because Eric already ruled it out as actually impossible.

    delegate void Circular1<T>(Circular2<T> param);

    delegate void Circular2<T>(Circular1<T> param);

    If Circular1 is covariant on T then Circular2 is contravariant on it, and vice versa. So simply stating that variance is intended is not sufficient, you really do have to spell out which way round.

    I could live with the "covariant on" / "contravariant on" syntax but I actually do think it’s helpful to spell out the meaning of it. Even after all eight of Eric’s blog posts on the subject, I still didn’t really fully grok it until I started thinking about it in terms of how IFoo<T> relates to IFoo<T’s base class> and IFoo<T’s derived classes>. Describing the meaning *concretely*, in terms of how it relates to the actual type you’re declaring, made a huge difference to my ability to understand it. I can actually write statements about Action and Meta and get their variance the right way round with this kind of syntax. Which isn’t true for any of the others, including "covariant on" and "contravariant on" – at least until I spent some time writing code using them. And I don’t think that *users* of the interfaces will ever spend enough time to get that understanding.

  50. Eric Lippert says:

    Thank you all for this fascinating discussion.  I have not yet absorbed nearly all of it. There is a wealth of possibilities here which I shall summarize and take to the design team at some point over the next few weeks.

    To answer the earlier question — the proposed feature is guaranteed-typesafe interface and delegate variance on reference types, no more, no less.  No variance on classes, at least not this go-round.  No unsafe variance.  No call-site variance, no virtual overload return type variance, etc.  Those are all features that we will consider, of course, but interface and delegate variance is the one I’m interested in today.

  51. Stefan Wenig says:

    sure, but do circular type references in delegates make any sense? do they even exist in the wild? how bad would it be if automatic co/contravariance would not work in this case, and you’d have to spell it out in those few cases (given there are any, and variance matters)?

    circular type references are quite common in interfaces, but in delegates?

    if that’s really a problem, an alternative solution would be automatic variance detection for delegates with opt-in, so that the compiler could produce an error message if you want variance, but it cannot find out which one.

    ok, back to the topic of spelling out the meaning. my problem with that is that, once I’ve found out the meaning, I need a way to remember it in an intuitive way, or I wouldn’t be able to use it. the difference between T:* and *:T, or worse, IFoo<T> assignable to IFoo<X> where T:X (as opposed to X:T), is something that I’ll have forgotten when I’m reading the next line of code. I’d have to grasp the concept that assignability of IFoo goes WITH assignability of T in the class hierarchy, or against it. co/contravariance spell this concept out. I can remember it, communicate it in whiteboard sessions, etc.

    T:* or *:T are just obvious while I’m looking at exactly this line of code. I guess it would be the same for base(T) and derived(T).

    + and – are better, but still worse than covariance/contravariance IMO.

    ok, we have to separate our concerns here.

    1) which one is easiest to write?

    2) which one is easiest to grasp when you’re not familiar with co/contravariance?

    3) how important is it to grasp the intention of the author when you’re not familiar with variance? would you typically not just try it and fix it if your assignments fail? (after all, who knows assignment compatibility rules of arrays? still, they’re being used every day)

    4) which one is easiest to work with once you’re familiar with variance?

    we need to set priorities on these problems.

    I think priority #1 should be that programmers familiar with variance get a syntax that is logical to read AND write. everyone else is probably best served with trial and error.

    in/out (as well as automatic detection) would be easier to write, but you’d still have to think about how this translates to variance when using those types. in fact, any constraints-based syntax would have this disadvantage.

    so besides being overly verbose, specifying that I’m only using T by using Action<T> as an input might be easier to write, but I’d have to think much harder to understand how this affects assignability of the Meta delegate type. this is probably a shot in the foot.

    (note: if I’d have automatic detection for delegates, decompilers/help generators could still spell it out for me using the "normal" covariant on/contravariant on syntax.)

  52. Stefan Wenig says:

    Eric, you’re welcome, but I’d really love to see you take part in that discussion here, and not just take it to the Holy Halls, disappear for a few years just to announce something way later that might or might include what we thought up here. I love your blog, but it’s really sometimes a bit frustrating that you leave a lot of (sometimes thoughtful, I like to think) comments unanswered.

    I’m fully aware that you don’t want to democratize language design, but that’s a different story.

    Back to the topic. I’m no longer sure that we need to make it clear that the co/contravariance clause describes a quality of the interface (as opposed to the type parameter), because I believe this is pretty much commutative and ultimately doesn’t matter.

    Still, I think you should separate this information out of the angle brackets, because it’s just too much information in a single place. We dont write IFoo<class T, struct U> either. How about that?

    interface IFoo<T,U>

     where T: class

     where U: struct

     covariant T

    (I could live with unmodified options 3 and 4 too, these are just minor flaws IMO, and ultimately matters of taste)

    quote:

    C# Language Specification

    12.5 Array covariance

    For any two reference-types A and B, if an implicit reference conversion (Section 6.1.4) or explicit reference conversion (Section 6.2.3) exists from A to B, then the same reference conversion also exists from the array type A[R] to the array type B[R], where R is any given rank-specifier (but the same for both array types). This relationship is known as array covariance. Array covariance in particular means that a value of an array type A[R] may actually be a reference to an instance of an array type B[R], provided an implicit reference conversion exists from B to A.

    I believe this way explanation is much easier to understand than anything that involves bigger and smaller types.

    Covariance: If you can assign A to B, you can assign IFoo<A> to IFoo<B>

    Contravariance: If you can assign A to B, you can assign IFoo<B> to IFoo<A>

    I believe that apart from automatic detection, it doesn’t get any easier than this. It’s relatively easy to understand, and once you’ve grasped it, very easy to remember. (That would rule out option 1 btw.)

    Option 2 and all suggestions involving "T:*", "X where T:X", "X where T is X", "base(T)" etc. are just an attempt to put a formal specification of co/contravariance into the declaration syntax. although I came up with a few of them myselves, those would hurt MY brain when I’d be busy _using_ co/contravariance in the course of solving another problem instead of thinking about it on its own.

    Please let’s not let the discussion about automatic detection in delegates die. The more I think about it, the more I like it.

  53. Thomas Krause says:

    What about introducing an accept clause:

    interface Foo<T>

    accept base for[/on/of] T

    Or:

    interface Foo<T>

    accept derrived for[/on/of] T

  54. Stefan Wenig says:

    Thomas,

    I liked that at the first look. However, I think you have to know what co/contravariance is in order to understand this. Otherwise, it would be misleading. Just reading your syntax and not knowing about variance, I’d assume that this somehow indicates that an IFoo<Mammal> can take an Animal object (for accept base/contravariance), which is nonsense, or a Giraffe object (for accept derived/covariance), which goes without saying.

  55. Welcome to the thirty-fifth edition of Community Convergence. We have an interesting and controversial

  56. Stefan’s idea of automatic variance for delegates is growing on me. If you automatically assign variance in the cases where it can be unambiguously determined (which is 99% of cases) but also allow it to be specified manually using the same syntax that interfaces use, that seems to fit well with the "simple things should be simple; complex things should be possible" principle.

    I’m not sure you even need a syntax to explicitly specify that a delegate type should NOT be variant when it could be. You don’t want it to be automatic on interfaces because of the fact that adding members to the interface could inadvertantly change the variance – and adding members to an interface is not normally a breaking change for consumers of the interface (although it is for implementers). I’m not sure there’s any equivalent to that for delegates – ANY change to a delegate is a breaking change for consumers of the delegate. So if the variance changes too in that case it’s not a big deal as all code consuming the delegate is already broken.

  57. Stefan Wenig says:

    Stuart, exactly. The chance of a delegate signature breaking assignability and the chance of a modification to an interface’s body causing this are two very different things. Plus, for the remaining 1% of delegates (if it’s really that much), figuring out sensible variance declarations even manually would probably make my brain explode. delegate Circular1<T> (Circular2<T>) … – I wasn’t even sure this would compile until I tried it 😉

    I guess there are languages out there who provide some sort of variance on higher-order functions with type parameters. I doubt that they have explicit syntax for this, but in any case looking at them (and talking to users of these languages) could be helpful.

    Does anybody know any such language?

  58. murman says:

    While scanning the examples, something like T:*,  *:U really draws my attention to the * in the font my browser uses. I’m wondering if something like T:var, var:U might be better. Does var have the right connotation linking the generic’s type to its variance?

  59. Paul Greene says:

    You could combines Lukes idea with the F# way and use underscores instead of asterisks:

    delegate R Func<_ is A, R is _> (A a)

  60. Stefan Wenig says:

    hey, if * looks bad in murman’s font, shouldn’t we make this symbol configurable? 😉

  61. Personally, I like #1 and #5. I haven’t read the comments… perhaps some much smarter syntax was introduced there.

  62. Dr Pizza says:

    "But covariance and contravariance are not simple things."

    Yes they are, or at least, they certainly should be.  The +/- syntax is far and away the best.  I think it corresponds nicely to the "or bigger (more general)" and "or smaller (less general)" concept, it’s already used in other languages, and it’s already used in the spec.

    I don’t really understand what you might be doing where you want use-site Java-style wildcarding more than covariant and contravariant generic arguments.

  63. avi_farah@hotmail.com says:

    Instead of [in] and [out] or [+] and [-] we may have a keyword that describe the "bigness"/"smallness" of covariance/contravariance-ness.  When you explained the terms you have explained it using the "Big" metaphor, similarly we can use keywords like [encompasses] and [encompassed].  Those keywords may be long 11 characters differing by the last letter "s" and "d", though I like the concept.  [surrounds] and [surrounded] are 9 and 10 characters, better but not ideal.  However, we can expand and as of now my favorite is: [enfold] and [strip].  They are different enough that they will not cause likely mistakes even with dyslexic folks like me who can see "North" and interpret "South" when driving on the highway.  Thank you for your attention –Avi Farah

  64. Benjamin Ernst says:

    I have not yet read the whole bunch of comments and suggestions, but i would prefer something like this:

    delegate R Func<atleast A, atmost R> (A a)

    It has the benefit that it avoids the fancy "*", plus it is easy readable once the programmer understood the concept of inheritance and specializing through inheritance.

  65. Quiz says:

    delegate R Func<_ is A, R is _> (A a)

    is good

    another option:

    delegate R Func<A, R> (A a)

      where R>A

  66. So nicely step by step blogged by Eric Lippert for &quot;Covariance and Contravariance&quot; as &quot;Fabulous