Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Curiously recurring template patterns with additional generic types

Tags:

c#

generics

crtp

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?

like image 961
Alexey Avatar asked Apr 23 '15 15:04

Alexey


1 Answers

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
like image 107
Jon Egerton Avatar answered Sep 21 '22 01:09

Jon Egerton