Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Assignments According to Covariance and Contravariance in Generics

0.00/5 (No votes)
11 Sep 2014 1  
Just a quick sample to clarify what we can do and what we can't

Introduction

Speaking with some younger colleagues, I realized that often the concepts of covariance and contravariance are not completely assimilated.

Referring to the official documentation for the correct explanation, this tip is only meant to facilitate understanding of these concepts through simple examples in a few lines of code, indeed I remember that I really understood these concepts only after I started to read and write some code about it.

How to Manipulate Generic Covariant and Contravariant Delegates (and Interfaces)

For the following examples, we just need to declare two empty classes: Base and Derived (where Derived implements Base).

class Base {  }

class Derived : Base {  }

I considered assignments for delegates with:

  • a covariant type parameter: Func<out TResult>
  • a contravariant type parameter: Action<in T>
  • both covariant and contravariant type parameters: Func<in T, out TResult>

Please note that all samples are with delegates but for interfaces is the same thing.

namespace Bagnasco.Samples.Variance
{
    using System;

    class Base { }

    class Derived : Base { }

    /// <summary>
    /// Sample class just for clarifying variance concepts
    /// (see: http://msdn.microsoft.com/it-it/library/dd799517(v=vs.110).aspx)
    /// </summary>
    class VarianceAssignmentsSample
    {
        public void VarianceSample()
        {
            Base baseInstance = new Base();
            Derived derivedInstance = new Derived();

            //Ok (ordinary polymorphism)
            baseInstance = derivedInstance;

            //Type parameters signature: <out TResult>
            Func<Base> funcThatReturnsBase = () => baseInstance;
            Func<Derived> funcThatReturnsDerived = () => derivedInstance;

            //Ok, mutch like ordinary polymorphism (TResult is covariant)
            funcThatReturnsBase = funcThatReturnsDerived;

            //Not allowed (TResult is more derived in funcThatReturnsDerived and is not contravariant)
            //funcThatReturnsDerived = funcThatReturnsBase;

            //Type parameters signature: <in T>
            Action<Base> actionThatTakesBase = _ => { };
            Action<Derived> actionThatTakesDerived = _ => { };

            //Ok (T is contravariant)
            actionThatTakesDerived = actionThatTakesBase;

            //Not allowed (T is not covariant and is less derived in actionThatTakesBase)
            //actionThatTakesBase = actionThatTakesDerived

            //Type parameters signature: <in T, out TResult>
            Func<Base, Base> functionThatTakesBaseAndReturnsBase = _ => baseInstance;
            Func<Base, Derived> functionThatTakesBaseAndReturnsDerived = _ => derivedInstance;
            Func<Derived, Base> functionThatTakesDerivedAndReturnsBase = _ => baseInstance;
            Func<Derived, Derived> functionThatTakesDerivedAndReturnsDerived = _ => derivedInstance;

            //Ok (TResult is covariant and in functionThatTakesBaseAndReturnsBase is less derived)
            functionThatTakesBaseAndReturnsBase = functionThatTakesBaseAndReturnsDerived;

            //Not allowed
            //(T is not covariant and in functionThatTakesBaseAndReturnsBase is less derived)
            //functionThatTakesBaseAndReturnsBase = functionThatTakesDerivedAndReturnsBase;
            //functionThatTakesBaseAndReturnsBase = functionThatTakesDerivedAndReturnsDerived;

            //Not allowed
            //(TResult is covariant and in functionThatTakesBaseAndReturnsDerived is more derived)
            //functionThatTakesBaseAndReturnsDerived = functionThatTakesBaseAndReturnsBase;            

            //Not allowed
            //(T is contravariant and in functionThatTakesBaseAndReturnsDerived is less derived
            //functionThatTakesBaseAndReturnsDerived = functionThatTakesDerivedAndReturnsDerived;

            //Not allowed
            //(T is contravariant and in functionThatTakesBaseAndReturnsDerived is less derived,
            // TResult is covariant and in functionThatTakesBaseAndReturnsDerived is more derived)
            //functionThatTakesBaseAndReturnsDerived = functionThatTakesDerivedAndReturnsBase;

            //All assignments are allowed
            //(T is contravariant and in functionThatTakesDerivedAndReturnsBase is more derived,
            // TResult is covariant and in functionThatTakesDerivedAndReturnsBase is less derived)
            functionThatTakesDerivedAndReturnsBase = functionThatTakesBaseAndReturnsBase;
            functionThatTakesDerivedAndReturnsBase = functionThatTakesBaseAndReturnsDerived;
            functionThatTakesDerivedAndReturnsBase = functionThatTakesDerivedAndReturnsDerived;

            //Ok (T is contravariant and in functionThatTakesDerivedAndReturnsDerived is more derived)
            functionThatTakesDerivedAndReturnsDerived = functionThatTakesBaseAndReturnsDerived;

            //Not allowed
            //(TResult is covariant and in functionThatTakesDerivedAndReturnsDerived is more derived)
            //functionThatTakesDerivedAndReturnsDerived = functionThatTakesBaseAndReturnsBase;            
            //functionThatTakesDerivedAndReturnsDerived = functionThatTakesDerivedAndReturnsBase;
        }
    }
}

I hope this will help to better understand covariance and contravariance in Generics.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here