Understanding Generic Invariance, Covariance and Contravariance

Viewed 184

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()?

0 Answers
Related