Covariant and Contravariant Generics

Content:

  1. Introduction
  2. What it is
  3. Examples
  4. Standard generics
  5. Co and Contra generics
  6. Conclusion

Introduction:

Usualy generics are pretty easy to understand. Wherever the class or method use the generic type T one just fill in the actual type.

But when we start talking about covariant and contravariant generic types then it become much haeder to understand.

Or at least is it for me. I am probably not the only one, so this article will try explain how it works and why.

What it is:

We have four different types of generics:

normal
<T>
T is any type
normal with constraint
<T extends X> / <T> ... where T : X
T is constrained so that it is X or a subclass of X
covariant
<? extends X> / <out X>
can be used with X or a subclass of X
contravariant
<? super X> / <in X>
can be used with X or a superclass of X

Note that "normal with constraint" is also covariant, but most languages have different syntax for the two.

Examples:

We will see examples in:

The syntaxes are:

Java C# Scala Kotlin
normal <T> <T> [T] <T>
normal with constraint <T extends X> <T> ... where T : X [T <: X] <T : X>
covariant <? extends X> <out X> [+T] <out X>
contravariant <? super X> <in X> [-T] <in X>

The languages are sligtly different, but I will try and make the same examples in each language.

Standard generics:

package stdgen;

public class StdGen {
    // **** demo classes ****
    public static class P {
        public void mP() { }
    }
    public static class C extends P {
        public void mC() { }
    }
    public static class Container<T> {
        private T o;
        public void put(T o) { this.o = o; }
        public T get() { return o; }
    }
    // **** standard generic methods ****
    public static <T> void test(Container<T> co, T o) {
        //co.put(new P()); // no required relation between T and P
        //co.put(new C()); // no required relation between T and C
        co.put(o);
        //P pcopy = co.get(); // no required relation between T and P
        //pcopy.mP();
        //pcopy.mC();
        //C ccopy = co.get(); // no required relation between T and C
        //ccopy.mP();
        //ccopy.mC();
        T tcopy = co.get();
        //tcopy.mP();
        //tcopy.mC();
    }
    public static <T extends P> void testp(Container<T> co, T o) {
        //co.put(new P()); // problem if T is C or a subclass of C
        //co.put(new C()); // problem if T is a subclass of C
        co.put(o);
        P pcopy = co.get();
        pcopy.mP();
        //pcopy.mC();
        //C ccopy = co.get(); // problem if T is P
        //ccopy.mP();
        //ccopy.mC();
        T tcopy = co.get();
        tcopy.mP();
        //tcopy.mC();
    }
    public static <T extends C> void testc(Container<T> co, T o) {
        //co.put(new P()); // problem if T is C or a subclass of C
        //co.put(new C()); // problem if T is a subclass of C
        co.put(o);
        P pcopy = co.get();
        pcopy.mP();
        //pcopy.mC();
        C ccopy = co.get();
        ccopy.mP();
        ccopy.mC();
        T tcopy = co.get();
        tcopy.mP();
        tcopy.mC();
    }
    // ****
    public static void main(String[] args) {
        test(new Container<P>(), new P());
        test(new Container<C>(), new C());
        testp(new Container<P>(), new P());
        testp(new Container<C>(), new C());
        //testc(new Container<P>(), new P()); // P does not match "T extends C"
        testc(new Container<C>(), new C());
    }
}
using System;

namespace StdGen
{
    public class Program
    {
        // **** demo classes ****
        public class P
        {
            public void MP() { }
        }
        public class C : P
        {
            public void MC() { }
        }
        public class Container<T>
        {
            private T o;
            public void Put(T o) { this.o = o; }
            public T Get() { return o; }
        }
        // **** standard generic methods ****
        public static void Test<T>(Container<T> co, T o)
        {
            //co.Put(new P()); // no required relation between T and P
            //co.Put(new C()); // no required relation between T and C
            co.Put(o);
            //P pcopy = co.Get(); // no required relation between T and P
            //pcopy.MP();
            //pcopy.MC();
            //C ccopy = co.Get(); // no required relation between T and C
            //ccopy.MP();
            //ccopy.MC();
            T tcopy = co.Get();
            //tcopy.MP();
            //tcopy.MC();
        }
        public static void TestP<T>(Container<T> co, T o) where T : P
        {
            //co.Put(new P()); // problem if T is C or a subclass of C
            //co.Put(new C()); // problem if T is a subclass of C
            co.Put(o);
            P pcopy = co.Get();
            pcopy.MP();
            //pcopy.MC();
            //C ccopy = co.Get(); // problem if T is P
            //ccopy.MP();
            //ccopy.MC();
            T tcopy = co.Get();
            tcopy.MP();
            //tcopy.MC();
        }
        public static void TestC<T>(Container<T> co, T o) where T : C
        {
            //co.Put(new P()); // problem if T is C or a subclass of C
            //co.Put(new C()); // problem if T is a subclass of C
            co.Put(o);
            P pcopy = co.Get();
            pcopy.MP();
            //pcopy.MC();
            C ccopy = co.Get();
            ccopy.MP();
            ccopy.MC();
            T tcopy = co.Get();
            tcopy.MP();
            tcopy.MC();
        }
        // ****
        public static void Main(string[] args)
        {
            Test(new Container<P>(), new P());
            Test(new Container<C>(), new C());
            TestP(new Container<P>(), new P());
            TestP(new Container<C>(), new C());
            //TestC(new Container<P>(), new P()); // P does not match "T : C"
            TestC(new Container<C>(), new C());
        }
    }
}
package stdgen

// **** demo classes ****
class P {
  def mP() { }
}
class C extends P {
  def mC() { }
}
class Container[T] {
  var o: Option[T] = None
  def put(o: Option[T]) { this.o = o }
  def get(): Option[T] = o
}
object StdGen {
  // **** standard generic methods ****
  def test[T](co: Container[T], o: Option[T]) {
    //co.put(Some(new P())) // no required relation between T and P
    //co.put(Some(new C())) // no required relation between T and P
    co.put(o)
    //val pcopy: Option[P] = co.get() // no required relation between T and P
    //pcopy.get.mP()
    //pcopy.get.mC()
    //val ccopy: Option[C] = co.get() // no required relation between T and P
    //ccopy.get.mP()
    //ccopy.get().mC()
    val tcopy: Option[T] = co.get()
    //tcopy.get.mP()
    //tcopy.get.mC()
  }
  def testp[T <: P](co: Container[T], o: Option[T]) {
    //co.put(Some(new P())) // problem if T is C or a subclass of C
    //co.put(Some(new C())) // problem if T is a subclass of C
    co.put(o)
    val pcopy: Option[P] = co.get()
    pcopy.get.mP()
    //pcopy.get.mC()
    //val ccopy: Option[C] = co.get() // problem if T is P
    //ccopy.get.mP()
    //ccopy.get().mC()
    val tcopy: Option[T] = co.get()
    tcopy.get.mP()
    //tcopy.get.mC()
  }
  def testc[T <: C](co: Container[T], o: Option[T]) {
    //co.put(Some(new P())) // problem if T is C or a subclass of C
    //co.put(Some(new C())) // problem if T is a subclass of C
    co.put(o)
    val pcopy: Option[P] = co.get()
    pcopy.get.mP()
    //pcopy.get.mC()
    val ccopy: Option[C] = co.get()
    ccopy.get.mP()
    ccopy.get.mC()
    val tcopy: Option[T] = co.get()
    tcopy.get.mP()
    tcopy.get.mC()
  }
  // ****
  def main(args: Array[String]): Unit = {
    testp(new Container[P](), Some(new P()));
    testp(new Container[C](), Some(new C()));
    //testc(new Container<P>(), Some(new P())); // P does not match "T extends C"
    testc(new Container[C](), Some(new C()));
  }
}
package stdgen

// **** demo classes ****
open class P {
    fun mP() { }
}
open class C : P() {
    fun mC() { }
}
class Container<T> {
    var o: T? = null
    fun put(o: T?) { this.o = o }
    fun get(): T? { return o }
}
// **** standard generic methods ****
fun <T> test(co: Container<T>, o: T) {
    //co.put(P()) // no required relation between T and P
    //co.put(C()) // no required relation between T and P
    co.put(o)
    //val pcopy: P? = co.get() // no required relation between T and P
    //pcopy?.mP()
    //pcopy?.mC()
    //val ccopy: C? = co.get() // no required relation between T and P
    //ccopy?.mP()
    //ccopy?.mC()
    val tcopy: T? = co.get()
    //tcopy?.mP()
    //tcopy?.mC()
}
fun <T : P> testp(co: Container<T>, o: T) {
    //co.put(P()) // problem if T is C or a subclass of C
    //co.put(C()) // problem if T is a subclass of C
    co.put(o)
    val pcopy: P? = co.get()
    pcopy?.mP()
    //pcopy?.mC()
    //val ccopy: C? = co.get() // problem if T is P
    //ccopy?.mP()
    //ccopy?.mC()
    val tcopy: T? = co.get()
    tcopy?.mP()
    //tcopy?.mC()
}
fun <T : C> testc(co: Container<T>, o: T) {
    //co.put(P()) // problem if T is C or a subclass of C
    //co.put(C()) // problem if T is a subclass of C
    co.put(o)
    val pcopy: P? = co.get()
    pcopy?.mP()
    //pcopy?.mC()
    val ccopy: C? = co.get()
    ccopy?.mP()
    ccopy?.mC()
    val tcopy: T? = co.get()
    tcopy?.mP()
    tcopy?.mC()
}
// ****
fun main() {
    test(Container<P>(), P())
    test(Container<C>(), C())
    testp(Container<P>(), P())
    testp(Container<C>(), C())
    //testc(Container<P>(), P()) // P does not match "T extends C"
    testc(Container<C>(), C())
}
Imports System

Namespace StdGen
    Public Class Program
        ' **** demo classes ****
        Public Class P
            Public Sub MP()
            End Sub
        End Class
        Public Class C
            Inherits P
            Public Sub MC()
            End Sub
        End Class
        Public Class Container(Of T)
            Private o As T
            Public Sub Put(o As T)
                Me.o = o
            End Sub
            Public Function [Get]() As T
                Return o
            End Function
        End Class
        ' **** standard generic methods ****
        Public Shared Sub Test(Of T)(co As Container(Of T), o As T)
            'co.Put(new P()); // no required relation between T and P
            'co.Put(new C()); // no required relation between T and C
            co.Put(o)
            'P pcopy = co.Get(); // no required relation between T and P
            'pcopy.MP();
            'pcopy.MC();
            'C ccopy = co.Get(); // no required relation between T and C
            'ccopy.MP();
            'ccopy.MC();
            Dim tcopy As T = co.[Get]()
            'tcopy.MP();
            'tcopy.MC();
        End Sub
        Public Shared Sub TestP(Of T As P)(co As Container(Of T), o As T)
            'co.Put(new P()); // problem if T is C or a subclass of C
            'co.Put(new C()); // problem if T is a subclass of C
            co.Put(o)
            Dim pcopy As P = co.[Get]()
            pcopy.MP()
            'pcopy.MC();
            'C ccopy = co.Get(); // problem if T is P
            'ccopy.MP();
            'ccopy.MC();
            Dim tcopy As T = co.[Get]()
            tcopy.MP()
            'tcopy.MC();
        End Sub
        Public Shared Sub TestC(Of T As C)(co As Container(Of T), o As T)
            'co.Put(new P()); // problem if T is C or a subclass of C
            'co.Put(new C()); // problem if T is a subclass of C
            co.Put(o)
            Dim pcopy As P = co.[Get]()
            pcopy.MP()
            'pcopy.MC();
            Dim ccopy As C = co.[Get]()
            ccopy.MP()
            ccopy.MC()
            Dim tcopy As T = co.[Get]()
            tcopy.MP()
            tcopy.MC()
        End Sub
        ' ****
        Public Shared Sub Main(args As String())
            Test(New Container(Of P)(), New P())
            Test(New Container(Of C)(), New C())
            TestP(New Container(Of P)(), New P())
            TestP(New Container(Of C)(), New C())
            'TestC(new Container<P>(), new P()); // P does not match "T : C"
            TestC(New Container(Of C)(), New C())
        End Sub
    End Class
End Namespace

We see that:

put P put C put T get P get C get T call with P call with C
normal - - OK - - OK OK OK
normal with constraint P - - OK OK - OK OK OK
normal with constraint C - - OK OK OK OK - OK

Co and Contra generics:

package cogen;

public class CoGen {
    // **** demo classes ****
    public static class P {
        public void mP() { }
    }
    public static class C extends P {
        public void mC() { }
    }
    public static class Container<T> {
        private T o;
        public void put(T o) { this.o = o; }
        public T get() { return o; }
    }
    // **** covariant methods ****
    public static void testp_extends(Container<? extends P> co) {
        //co.put(new P()); // problem if ? is C or a subclass of C
        //co.put(new C()); // problem if ? is a subclass of C
        P pcopy = co.get();
        pcopy.mP();
        //pcopy.mC();
        //C ccopy = co.get(); // problem if ? is P
        //ccopy.mP();
        //ccopy.mC();
    }
    public static void testc_extends(Container<? extends C> co) {
        //co.put(new P()); // problem if ? is C or a subclass of C
        //co.put(new C()); // problem if ? is a subclass of C
        P pcopy = co.get();
        pcopy.mP();
        //pcopy.mC();
        C ccopy = co.get();
        ccopy.mP();
        ccopy.mC();
    }
    // **** contravariant methods ****
    public static void testp_super(Container<? super P> co) {
        co.put(new P());
        co.put(new C());
        //P pcopy = co.get(); // problem if ? is a superclass of P
        //pcopy.mP();
        //pcopy.mC();
        //C ccopy = co.get(); // problem if ? is P or a superclass of P
        //ccopy.mP();
        //ccopy.mC();
    }
    public static void testc_super(Container<? super C> co) {
        //co.put(new P()); // problem if ? is C
        co.put(new C());
        //P pcopy = co.get(); // problem if ? is a superclass of P
        //pcopy.mP();
        //pcopy.mC();
        //C ccopy = co.get(); // problem if ? is P or a superclass of P
        //ccopy.mP();
        //ccopy.mC();
    }
    // ****
    public static void main(String[] args) {
        testp_extends(new Container<P>());
        testp_extends(new Container<C>());
        //testc_extends(new Container<P>()); // P does not match "? extends C"
        testc_extends(new Container<C>());
        testp_super(new Container<P>());
        //testp_super(new Container<C>()); // C does not match "? super P"
        testc_super(new Container<P>());
        testc_super(new Container<C>());
    }
}
using System;

namespace CoGen
{
    public class Program
    {
        // **** demo classes and interfaces ****
        public class P
        {
            public void MP() { }
        }
        public class C : P
        {
            public void MC() { }
        }
        public interface IReadonlyContainer<out T>
        {
            //void Put(T o); // in arg does not match "out"
            T Get();
        }
        public interface IWriteonlyContainer<in T>
        {
            void Put(T o);
            //T Get(); // return value does not match "in"
        }
        public class Container<T> : IReadonlyContainer<T>, IWriteonlyContainer<T>
        {
            private T o;
            public void Put(T o) { this.o = o; }
            public T Get() { return o; }
        }
        // **** covariant methods ****
        public static void TestP_ReadOnly(IReadonlyContainer<P> co)
        {
            //co.Put(new P()); // problem if C or a subclass of C
            //co.Put(new C()); // problem if a subclass of C
            P pcopy = co.Get();
            pcopy.MP();
            //pcopy.MC();
            //C ccopy = co.Get(); // problem if P
            //ccopy.MP();
            //ccopy.MC();
        }
        public static void TestC_ReadOnly(IReadonlyContainer<C> co)
        {
            //co.Put(new P()); // problem if C or a subclass of C
            //co.Put(new C()); // problem if a subclass of C
            P pcopy = co.Get();
            pcopy.MP();
            //pcopy.MC();
            C ccopy = co.Get();
            ccopy.MP();
            ccopy.MC();
        }
        // **** contravariant methods ****
        public static void TestP_WriteOnly(IWriteonlyContainer<P> co)
        {
            co.Put(new P());
            co.Put(new C());
            //P pcopy = co.Get(); // problem if superclass of P
            //pcopy.MP();
            //pcopy.MC();
            //C ccopy = co.Get(); // problem if P or a superclass of P
            //ccopy.MP();
            //ccopy.MC();
        }
        public static void TestC_WriteOnly(IWriteonlyContainer<C> co)
        {
            //co.Put(new P()); // problem if C
            co.Put(new C());
            //P pcopy = co.Get(); // problem if superclass of P
            //pcopy.MP();
            //pcopy.MC();
            //C ccopy = co.Get(); // problem if P or a superclass of P
            //ccopy.MP();
            //ccopy.MC();
        }
        // *****
        public static void Main(string[] args)
        {
            TestP_ReadOnly(new Container<P>());
            TestP_ReadOnly(new Container<C>());
            //TestC_ReadOnly(new Container<P>()); // P does not match "out C"
            TestC_ReadOnly(new Container<C>());
            TestP_WriteOnly(new Container<P>());
            //TestP_WriteOnly(new Container<C>()); // C does not match "in P"
            TestC_WriteOnly(new Container<P>());
            TestC_WriteOnly(new Container<C>());
        }
    }
}
package cogen

// **** demo classes ****
class P {
  def mP() { }
}
class C extends P {
  def mC() { }
}
abstract class ReadonlyContainer[+T] {
  //def put(o: Option[T]) // in arg does not match "out"
  def get(): Option[T]
}
abstract trait WriteonlyContainer[-T] {
  def put(o: Option[T])
  //def get(): Option[T] // return value does not match "in"
}
class Container[T] extends ReadonlyContainer[T] with WriteonlyContainer[T] {
  var o: Option[T] = None
  def put(o: Option[T]) { this.o = o }
  def get(): Option[T] = o
}
object CodeGen {
    // **** covariant methods ****
    def testp_readonly(co: ReadonlyContainer[P]) {
        //co.put(new P()) // problem if C or a subclass of C
        //co.put(new C()) // problem if a subclass of C
        val pcopy: Option[P] = co.get()
        pcopy.get.mP()
        //pcopy.get.mC()
        //val ccopy: Option[C] = co.get() // problem if P
        //ccopy.get.mP()
        //ccopy.get.mC()
    }
    def testc_readonly(co: ReadonlyContainer[C]) {
        //co.put(new P()) // problem if C or a subclass of C
        //co.put(new C()) // problem if a subclass of C
        val pcopy: Option[P] = co.get()
        pcopy.get.mP()
        //pcopy.get.mC()
        val ccopy: Option[C] = co.get()
        ccopy.get.mP()
        ccopy.get.mC()
    }
    // **** contravariant methods ****
    def testp_writeonly(co: WriteonlyContainer[P]) {
        co.put(Some(new P()))
        co.put(Some(new C()))
        //val pcopy: Option[P] = co.get() // problem if a superclass of P
        //pcopy.get.mP()
        //pcopy.get.mC()
        //val ccopy: Option[C] = co.get() // problem if P or a superclass of P
        //ccopy.get.mP()
        //ccopy.get.mC()
    }
    def testc_writeonly(co: WriteonlyContainer[C]) {
        //co.put(Some(new P())) // problem if C
        co.put(Some(new C()))
        //val pcopy: Option[P] = co.get() // problem if a superclass of P
        //pcopy.get.mP()
        //pcopy.get.mC()
        //val ccopy: Option[C] = co.get() // problem if P or a superclass of P
        //ccopy.get.mP()
        //ccopy.get.mC()
    }
  // ****
  def main(args: Array[String]): Unit = {
        testp_readonly(new Container[P]())
        testp_readonly(new Container[C]())
        //testc_readonly(new Container[P]()) // P does not match "+C"
        testc_readonly(new Container[C]())
        testp_writeonly(new Container[P]())
        //testp_writeonly(new Container[C]()) // C does not match "-P"
        testc_writeonly(new Container[P]())
        testc_writeonly(new Container[C]())
  }
}
package cogen

// **** demo classes ****
open class P {
    fun mP() { }
}
open class C : P() {
    fun mC() { }
}
interface ReadonlyContainer<out T> {
    //fun put(o: T?) // in arg does not match "out"
    fun get(): T?
}
interface WriteonlyContainer<in T> {
    fun put(o: T?)
    //fun get(): T? // return value does not match "in"
}
class Container<T> : ReadonlyContainer<T>, WriteonlyContainer<T> {
    var o: T? = null
    override fun put(o: T?) { this.o = o }
    override fun get(): T? { return o }
}
// **** covariant methods ****
fun testp_extends(co: ReadonlyContainer<P>) {
    //co.put(P()) // problem if C or a subclass of C
    //co.put(C()) // problem if a subclass of C
    val pcopy: P? = co.get()
    pcopy?.mP()
    //pcopy?.mC()
    //val ccopy: C? = co.get() // problem if P
    //ccopy?.mP()
    //ccopy?.mC()
}
fun testc_extends(co: ReadonlyContainer<C>) {
    //co.put(P()) // problem if C or a subclass of C
    //co.put(C()) // problem if a subclass of C
    val pcopy: P? = co.get()
    pcopy?.mP()
    //pcopy?.mC()
    val ccopy: C? = co.get()
    ccopy?.mP()
    ccopy?.mC()
}
// **** contravariant methods ****
fun testp_super(co: WriteonlyContainer<P>) {
    co.put(P())
    co.put(C())
    //val pcopy: P? = co.get() // problem if a superclass of P
    //pcopy?.mP()
    //pcopy?.mC()
    //val ccopy: C? = co.get() // problem if P or a superclass of P
    //ccopy?.mP()
    //ccopy?.mC()
}
fun testc_super(co: WriteonlyContainer<C>) {
    //co.put(P()) // problem if C
    co.put(C())
    //val pcopy: P? = co.get() // problem if a superclass of P
    //pcopy?.mP()
    //pcopy?.mC()
    //val ccopy: C? = co.get() // problem if P or a superclass of P
    //ccopy?.mP()
    //ccopy?.mC()
}
// ****
fun main() {
    testp_extends(Container<P>())
    testp_extends(Container<C>())
    //testc_extends(Container<P>()) // P does not match "out C"
    testc_extends(Container<C>())
    testp_super(Container<P>())
    //testp_super(Container<C>()) // C does not match "in P"
    testc_super(Container<P>())
    testc_super(Container<C>())
}
Imports System

Namespace CoGen
    Public Class Program
        ' **** demo classes and interfaces ****
        Public Class P
            Public Sub MP()
            End Sub
        End Class
        Public Class C
            Inherits P
            Public Sub MC()
            End Sub
        End Class
        Public Interface IReadonlyContainer(Of  Out T)
            'void Put(T o); // in arg does not match "out"
            Function [Get]() As T
        End Interface
        Public Interface IWriteonlyContainer(Of  In T)
            Sub Put(o As T)
            'T Get(); // return value does not match "in"
        End Interface
        Public Class Container(Of T)
            Implements IReadonlyContainer(Of T)
            Implements IWriteonlyContainer(Of T)
            Private o As T
            Public Sub Put(o As T) Implements Program.IWriteonlyContainer(Of T).Put
                Me.o = o
            End Sub
            Public Function [Get]() As T Implements Program.IReadonlyContainer(Of T).[Get]
                Return o
            End Function
        End Class
        ' **** covariant methods ****
        Public Shared Sub TestP_ReadOnly(co As IReadonlyContainer(Of P))
            'co.Put(new P()); // problem if C or a subclass of C
            'co.Put(new C()); // problem if a subclass of C
            Dim pcopy As P = co.[Get]()
            pcopy.MP()
            'pcopy.MC();
            'C ccopy = co.Get(); // problem if P
            'ccopy.MP();
            'ccopy.MC();
        End Sub
        Public Shared Sub TestC_ReadOnly(co As IReadonlyContainer(Of C))
            'co.Put(new P()); // problem if C or a subclass of C
            'co.Put(new C()); // problem if a subclass of C
            Dim pcopy As P = co.[Get]()
            pcopy.MP()
            'pcopy.MC();
            Dim ccopy As C = co.[Get]()
            ccopy.MP()
            ccopy.MC()
        End Sub
        ' **** contravariant methods ****
        Public Shared Sub TestP_WriteOnly(co As IWriteonlyContainer(Of P))
            co.Put(New P())
            co.Put(New C())
            'P pcopy = co.Get(); // problem if superclass of P
            'pcopy.MP();
            'pcopy.MC();
            'C ccopy = co.Get(); // problem if P or a superclass of P
            'ccopy.MP();
            'ccopy.MC();
        End Sub
        Public Shared Sub TestC_WriteOnly(co As IWriteonlyContainer(Of C))
            'co.Put(new P()); // problem if C
            co.Put(New C())
            'P pcopy = co.Get(); // problem if superclass of P
            'pcopy.MP();
            'pcopy.MC();
            'C ccopy = co.Get(); // problem if P or a superclass of P
            'ccopy.MP();
            'ccopy.MC();
        End Sub
        ' *****
        Public Shared Sub Main(args As String())
            TestP_ReadOnly(New Container(Of P)())
            TestP_ReadOnly(New Container(Of C)())
            'TestC_ReadOnly(new Container<P>()); // P does not match "out C"
            TestC_ReadOnly(New Container(Of C)())
            TestP_WriteOnly(New Container(Of P)())
            'TestP_WriteOnly(new Container<C>()); // C does not match "in P"
            TestC_WriteOnly(New Container(Of P)())
            TestC_WriteOnly(New Container(Of C)())
        End Sub
    End Class
End Namespace

We see that:

put P put C put T get P get C get T call with P call with C
covariant P - - OK - OK OK
covariant C - - OK OK - OK
contravariant P OK OK - - OK -
contravariant C - OK - - OK OK

Conclusion:

My recommendation is to avoid covariant and contravariant generic in ones own code if possible.

For external frameworks then one may often use it without even knowing it. The typical usage is readonly containers.

Article history:

Version Date Description
1.0 August 16th 2024 Initial version

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj