4

Variance in generics is something that has always confused me. The following rules are what I was led to believe:

  • Invariant T: Only accepts types of T; nothing more, nothing less.
  • Covariant T: Accepts types of T and types derived from T, but no less than T.
  • Contravariant T: Accepts types of T and types less than T, but not derived from T.

I'm guessing that's wrong, given the following code sample:

class Invariant<T>(value: T)
class Covariant<out T>(value: T)
class Contravariant<in T>(value: T)

open class A
open class B : A()
open class C : B()

class VarianceExample {
    val invariantA: Invariant<B> = Invariant(A()) // Error (as expected)
    val invariantB: Invariant<B> = Invariant(B()) // Okay (as expected)
    val invariantC: Invariant<B> = Invariant(C()) // Okay (wait, what!?)

    val covariantA: Covariant<B> = Covariant(A()) // Error (as expected)
    val covariantB: Covariant<B> = Covariant(B()) // Okay (as expected)
    val covariantC: Covariant<B> = Covariant(C()) // Okay (as expected)

    val contravariantA: Contravariant<B> = Contravariant(A()) // Okay (as expected)
    val contravariantB: Contravariant<B> = Contravariant(B()) // Okay (as expected)
    val contravariantC: Contravariant<B> = Contravariant(C()) // Okay (wait, what!?)
}

Notably...

  • val invariantC: Invariant<B> = Invariant(C())

Why does an Invariant<B> allow me to pass C()?

  • val contravariantC: Contravariant<B> = Contravariant(C())

Why does Contravariant<B> allow me to pass C()?

Matthew Layton
  • 39,871
  • 52
  • 185
  • 313
  • You aren't providing a type parameter for the constructor calls, so the compiler can infer something that works. When you say `Invariant(C())`, for example, you are constructing an `Invariant(C())` – Matt Timmermans Sep 28 '21 at 12:27
  • Check this question https://stackoverflow.com/questions/66410115/difference-between-variance-covaraince-contravariance-and-bivariance-in-typesc . I have linked several useful articles about *-variance – captain-yossarian from Ukraine Oct 22 '21 at 13:33
  • This is most certainly not a language-agnostic question as it was tagged. You're testing in a certain language to illustrate your confusion. Other languages might behave differently. – Robert Mikes Oct 29 '21 at 14:53
  • @RobertMikes how do you demonstrate the problem without examples written in a language? FYI, the same applied for C# – Matthew Layton Oct 29 '21 at 14:55
  • I'm not going to steal a blog post, but the following link (for C++) provides a pretty good illustration: https://quuxplusone.github.io/blog/2019/01/20/covariance-and-contravariance/ – Mark Moretto Nov 02 '21 at 01:00
  • 1
    @MatthewLayton The question doesn't make sense in a language-agnostic context, because the answers will have to be language-specific, different for every language. To use an analogy, you could ask a human-language-agnostic grammar question "how to put a verb in present continuous tense" which makes perfect sense in English (and some other languages), but no sense at all in many other languages. And in the languages where it makes sense, the answers will be widely different. So you should tag your question with the particular language(s) you're interested in. – Robert Mikes Nov 08 '21 at 05:53

0 Answers0