Let's say I want to have a generic Box
class that can contain something inside, so it's a Box<T>
. Box<T>
has a Transform
method that returns a Box<U>
:
public Box<U> Transform<U>(Func<T, U> transform)
So far this has been quite simple. However, I actually need an abstract Box
, since the way the values are boxed and transformed is implementation-specific. (I can't have an interface since there are other methods that are implemented via composition of abstract methods, but this doesn't change anything anyway).
Of course, I want my overridden Transform
methods to return an appropriate subclass of Box
, not Box
itself. Since return types of overriding methods are invariant in C#, I turn to the curiously recurring template pattern (see IComparable<T>
):
public abstract class Box<B, T> where B : Box<B, T>
Now every class I inherit from Box<B, T>
should refer to itself or all hell breaks loose:
public class FooBox<T> : Box<FooBox, T>
However, this completely destroys the Transform
method:
public abstract Box<B, U> Transform<U>(Func<T, U> transform);
fails to compile with The type 'B' cannot be used as type parameter 'B' in the generic type or method 'Test.Box<B,T>'. There is no implicit reference conversion from 'B' to 'Test.Box<B,U>'. (CS0311)
. That makes sense, since the return type is now Box<B, U>
and B is Box<B, T>
, which is not Box<B, U>
.
The straightforward fix won't work:
public abstract Box<B, U> Transform<U>(Func<T, U> transform) where B : Box<B, U>;
fails to compile with 'Test.Box<B,T>.Transform<U>()' does not define type parameter 'B' (CS0699)
.
Is there any way to resolve this, or have I really painted myself into a corner?
I think the problem with the straight forward fix is the reuse of the B
type parameter. Try something else, and include it as a type parameter:
public abstract Box<B2, U> Transform<B2,U>(Func<T, U> transform) where B2 : Box<B2, U>;
Update: you stated:
now I cannot guarantee that B2 and B are actually the same derived class, which was my goal
This isn't the case though, B2
does not (cannot?) inherit from B
, U
might inherit T
if you wish. You can include that as a constraint. That's not strictly necessary for the pattern though, since its up to the body of Transform
to sort it out.
e.g:
public abstract Box<B2, U> Transform<B2,U>(Func<T, U> transform)
where B2 : Box<B2, U>
where U : T
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