Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot implicitly convert child class to parent class in type parametrized method

I recently had to help troubleshoot an issue someone had with returning from a generic method, and while there were multiple problems to be solved, I understood and could explain all of them - except for the last hurdle of getting the compiler to accept the return type. Though I eventually succeeded in getting the program to compile and run correctly, I still can't completely follow the logic of why it had to be done this way.

I've reproduced the issue with a minimal example below. Given a very simple parent-child class structure with only a single generic method:

abstract class AbstractParentClass
{
    public abstract T DoThing<T>() where T : AbstractParentClass;
}

class ConcreteChildClass : AbstractParentClass
{
    public override T DoThing<T>()
    {
        return this;
    }
}

This will cause an error on the return line of Cannot implicitly convert type 'ConcreteChildClass' to 'T'. Slightly strange given that T is constrained to be an instance of AbstractParentClass and this trivially is one of those but okay, sure, so we'll do it explicitly:

public override T DoThing<T>()
{
    return (T) this;
}

Now the error message reads Cannot convert type 'ConcreteChildClass' to 'T'. What? If T were unconstrained then sure, I get that we couldn't guarantee it, but with an inheritance like we do, surely it should be a straightforward cast?

We can get close by explicitly casting to the parent class:

public override T DoThing<T>()
{
    return (AbstractParentClass) this;
}

Now the error reads Cannot implicitly convert type 'AbstractParentClass' to 'T'. An explicit conversion exists (are you missing a cast?). Why can we not implicitly convert to the exact type of the constraint - what possible situation could lead to a class of the constraint type to not be convertable to... itself? But at least this is solvable now, the error message even tells us directly how to do it:

public override T DoThing<T>()
{
    return (T)(AbstractParentClass) this;
}

And now everything runs just fine. We can even make it look slightly prettier if we want:

public override T DoThing<T>()
{
    return this as T;
}

Throughout all of this, if T were unconstrained, I of course understand why these conversions wouldn't be possible as written. But it is, and in a way that the compiler should not have any issue following. Does the compiler, for some reason, just not take type constraints into account for allowable implicit conversions? In my experience all situations like this in C# have a very good reason behind them, I just for the life of me can't figure it out for this one.

If anybody has any insight into why the compiler has trouble with this (seemingly!) very simple bit of code, I'd be thankful for an explanation of the problems with allowing these conversions.

like image 800
Sethur Avatar asked Jan 24 '23 06:01

Sethur


1 Answers

Becasue technically you could do:

class ConcreteChildClass2 : AbstractParentClass
{
    public override T DoThing<T>()
    {
        return null;
    }
}

var ccc = new ConcreteChildClass();
var ccc2 = ccc.DoThing<ConcreteChildClass2>();

Even with the casts you do, this will blow up at runtime:

System.InvalidCastException: Unable to cast object of type 'ConcreteChildClass' to type 'ConcreteChildClass2'.

If instead you do return this as T, you'll get null as a result instead of a casting exception.

In other words, the constraint does not guarantee that DoThing returns the same type as the defining class, it only guarantees that it returns some type that inherits AbstractParentClass.

like image 197
D Stanley Avatar answered Jan 31 '23 04:01

D Stanley