Search code examples
c#delegatescontravariance

Contravariance of delegate resulting in error of "Cannot convert from ... to ..."


In order to simplify, let say I have a parent and a child class:

public class MyParent { }
public class MyChild : MyParent { }

And this two function with some code:

public void DoSomethingBy_MyChild(MyChild myChild) { //code }
public void DoSomethingBy_MyParent(MyParent myParent) { //code }

But when I try this unit test of Action<MyChild> delegate with DoSomethingBy_MyParent with a MyParent param the compiler says:

Error CS1503 Argument 1: cannot convert from 'MyParent' to 'MyChild'.

public void UnitTest()
{            
    Action<MyChild> processor;
    processor = DoSomethingBy_MyChild;
    processor(new MyChild());             //OK

    processor = DoSomethingBy_MyParent; 
    processor(new MyChild());             //OK

    processor = DoSomethingBy_MyParent;
    processor(new MyParent());            //Error
}

Solution

  • From Using Variance in Delegates (C#):

    When you assign a method to a delegate, covariance and contravariance provide flexibility for matching a delegate type with a method signature. Covariance permits a method to have return type that is more derived than that defined in the delegate. Contravariance permits a method that has parameter types that are less derived than those in the delegate type.


    It is fine to assign DoSomethingBy_MyParent to processor (a contravariant assignment since MyParent is less-derived than MyChild) because anything which is MyChild is, by definition also MyParent:

    Action<MyChild> processor;
    processor = DoSomethingBy_MyParent;
    

    However, what happens when you then try to pass a MyParent into processor is effectively

    Action<MyChild> processor;
    processor(new MyParent());           
    

    This is not fine because processor requires a MyChild to be passed into it - it cannot be called contravariantly. It doesn't matter that you've assigned DoSomethingBy_MyParent to it - processor is declared as Action<MyChild> so it must receive an instance of MyChild or a more-derived type.


    To put it another way, you have

    public void DoSomethingBy_MyChild(MyChild myChild) { //code }
    

    and you wouldn't expect to be able to call it like this:

    DoSomethingBy_MyChild(new Parent());
    

    because method calls work covariantly (you can pass in an instance of a more-derived type), not contravariantly (you cannot pass in an instance of a less-derived type).