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