Modern C# Techniques, Part 1: Curiously Recurring Generic Pattern
• CommentsI’m starting a new series today looking at some modern C# techniques. Part of what I like about C# is that the language is always improving, and those improvements bring newer code patterns with them.
Today’s topic is not actually new, but many developers haven’t seen it before, so it’s worth taking a look at. As with many of the techniques I’ll be discussing, I’m not sure if this one has a name, so I am just calling it whatever I call it in my head.
The Curiously Recurring Generic Pattern
The “curious” name for this pattern comes from the C++ world, where it was called the Curiously Recurring Template Pattern. So in C# I just call it the Curiously Recurring Generic Pattern, since it’s essentially the same thing but with generics instead of templates. According to Wikipedia, it’s actually “F-bound polymorphism”, but I’m not going to remember that.
The Curiously Recurring Generic Pattern is when an interface (or base type) takes a generic parameter that is its own derived type. A simple example looks like this:
interface IExample<TDerived>
{
}
class MyExample : IExample<MyExample>
{
}
But Why Tho?
It essentially comes down to typing. If an interface (or base type) wants to use the full, derived type as a method parameter or return value, then it can define those methods itself without putting any burdern on the derived type.
Consider a familiar example from the .NET BCL: IEquatable<T>
. IEquatable<T>
is defined as thus:
public interface IEquatable<T>
{
bool Equals(T? other);
}
And it is used as such:
sealed class MyEquatable : IEquatable<MyEquatable>
{
public bool Equals(MyEquatable? other) { ... }
}
The thing to note here is that MyEquatable.Equals
implements IEquatable<T>.Equals
with a strongly-typed MyEquatable
argument. If the Curiously Recurring Generic Pattern wasn’t used, then IEquatable<T>
would just be IEquatable
(taking an object
argument), losing type safety and efficiency.
Adding a Generic Constraint
The interface (or base type) may also use itself as a generic constraint. It doesn’t have to (the examples above don’t), but sometimes it’s useful, particularly for base types. The Curiously Recurring Generic Pattern with generic constraints looks like this:
abstract class ExampleBase<TDerived>
where TDerived : ExampleBase<TDerived>
{
// Methods in here can use `(TDerived)this` freely.
// This is particularly useful if this interface wants to *return* a value of TDerived.
public virtual TDerived Something() => (TDerived)this;
}
class AnotherExample : ExampleBase<AnotherExample>
{
// Implicitly has `public AnotherExample Something();` defined.
// The base class method already has the correct return type.
// (Can still override if desired).
}
As noted in the comments above, this approach is useful if TDerived
is used as a return type. As one example, this is common with fluent APIs.
More generally, the generic constraint is needed in either of these situations:
- The base type needs to treat a
ExampleBase<TDerived>
instance (e.g.,this
) as its derived type (i.e.,(TDerived)this
). This can also come up when passingthis
to other methods. - The base type needs to treat a
TDerived
as aExampleBase<TDerived>
, e.g., calling private base methods on an instance of typeTDerived
other thanthis
. In this case no explicit cast is necessary.
CRGP and Default Interface Methods
Similar to regular interface methods, the Curiously Recurring Generic Pattern can enhance the type safety of default interface methods if necessary. This is similar to using CRGP with base types, except interfaces cannot have state. Put another way, this enables strongly-typed traits, but falls short of mixins.
CRGP and Static Interface Methods (and Operators)
One possibility for CRGP with static interface methods is to define operators (or other static methods) with the proper type signatures. Previously, CRGP required a base type to define operators (e.g., EquatableBaseWithOperators<TDerived>
in my Nito.Comparers library), but using CRGP with static interface methods allows strong typing for operator signatures (e.g., IUnaryNegationOperators<TSelf, TResult>
is an interface that defines operator-
with the proper type signature).
Misuse
Like other code patterns, the CRGP can be misused. IMO the most common misuse of this pattern is overuse. Bear in mind CRGP isn’t powerful enough to provide mixins - even with default interface methods (which provide traits, not mixins).
Also, CRGP tends to make the code more complex. There’s a tradeoff there, and you need to keep maintainability in mind.
Summary
The Curiously Recurring Generic Pattern isn’t actually new, and it isn’t often necessary, but it’s a nice tool to have when you do need it.