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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With