Using a generic (X) delegate in a generic (Y) method where Y extends some base class. Cannot convert to base class

public abstract class SomeBaseClass {}

public class SomeSpecificClass: SomeBaseClass {
    public int propertyA;
    public string propertyB;
}

public delegate void Callback<T>(T data);


public class Foo {


    void MethodA <T> (Callback<T> theInstance) where T: SomeBaseClass {
        MethodB(theInstance);
    }

    void MethodB(Callback<SomeBaseClass> theInstance) {

    }

    void MethodC() {
        Callback<SomeSpecificClass> cb = (data) => {};
        MethodA(cb);
    }

    void MethodD <T> (T theInstance) where T: SomeBaseClass {
        MethodE(theInstance);
    }

    void MethodE (SomeBaseClass theInstance) {

    }
}

Produces the error in MethodA:

Argument 1: cannot convert from 'Callback<T>' to 'Callback<SomeBaseClass>' [Assembly-CSharp] Callback<T> theInstance

But MethodD works fine passing its instance to MethodE

Why can't I pass the generic Callback<T> instance in MethodA to argument of type Callback<SomeBaseClass> in MethodB when I'm specifiying the constraint that T extends SomeBaseClass

Jon Skeet
people
quotationmark

Basically, you can't do this because it's not safe.

Suppose we have a concrete class derived from SomeBaseClass:

public class SomeOtherSpecificClass {}

Suppose we change your MethodB to:

void MethodB(Callback<SomeBaseClass> theInstance)
{
    theInstance(new SomeOtherSpecificClass());
}

That should compile, right? After all, you're just passing a SomeOtherSpecificClass into a Callback<SomeBaseClass>, which should be fine.

Then if I call MethodA like this:

Callback<SomeSpecificClass> callbcak = data => Console.WriteLine(data.propertyA);
MethodA(callback);

... then if all of that were allowed, we'd be passing a SomeOtherSpecificClass into a delegate expecting a SomeSpecificClass.

Your MethodD and MethodE examples are fine, because MethodE can only use members of SomeBaseClass... but a Callback<SomeSpecificClass> really requires a SomeSpecificClass, so you can't just treat it as if it were a method accepting a SomebaseClass.

To show this more simply:

// This is valid...
string text = "";
object obj = text;

// This isn't...
Action<string> stringAction = text => Console.WriteLine(text.Length);
Action<object> objectAction = stringAction;
// ... because it would allow this:
objectAction(new object());

people

See more on this question at Stackoverflow