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