Search code examples
c#contravariance

What am I missing in Microsoft's contravariance example?


I'm trying to understand contravariance in practice. It seemed to be straightforward when I read the book, but now I seem to have stuck. I understand there's a lot of topics on contravariance and I've googled many of them, none helped me understand this particular problem Here's what Microsoft docs say https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance

And here's my code:

using static System.Console;

namespace CSharpTests 
{
    class Program 
    {
        delegate void Action<T> (T obj);

        static void Main(string[] args) 
        {

            Action<Device> b = DeviceAction;
            Action<Mouse> d = b; // Error cannot implicitly convert type CSharpTests.Program.Action<CSharpTests.Device> to CSharpTests.Program.Action<CSharpTests.Mouse>
            d(new Mouse());

            ReadLine();
        }

        private static void DeviceAction(Device target) {
            WriteLine(target.GetType().Name);
        }
    }
    class Device { }
    class Mouse : Device { } 
}

What is the crucial difference? My code doesn't even compile. As you can see I've got a delegate accepting generic type which, as far as I understand, allows contravariance. But on practice, I got a compile time error. I also try to do it with the "out" parameter and got the same error

using static System.Console;

namespace CSharpTests {

    class Program {

        delegate void Action<T> (out T obj);

        static void Main(string[] args) {

            Action<Device> b = DeviceAction;
            Action<Mouse> d = b; // Error cannot implicitly convert type CSharpTests.Program.Action<CSharpTests.Device> to CSharpTests.Program.Action<CSharpTests.Mouse>
            Mouse m;
            d(out m);

            ReadLine();
        }

        private static void DeviceAction(out Device target) {
            target = new Device();
            WriteLine(target.GetType().Name);
        }
    }
    class Device { }
    class Mouse : Device { } 
}

Solution

  • Change the signature to delegate void Action<in T>(T arg).

    Declaring a type parameter as in indicates contravariance; out indicates covariance.

    You can usually tell which one to use because in is for inputs (e.g., parameters) and out is for outputs (e.g., return values).