How to do template specialization in C#

Viewed 73744

How would you do specialization in C#?

I'll pose a problem. You have a template type, you have no idea what it is. But you do know if it's derived from XYZ you want to call .alternativeFunc(). A great way is to call a specialized function or class and have normalCall return .normalFunc() while have the other specialization on any derived type of XYZ to call .alternativeFunc(). How would this be done in C#?

8 Answers

In C#, the closest to specialization is to use a more-specific overload; however, this is brittle, and doesn't cover every possible usage. For example:

void Foo<T>(T value) {Console.WriteLine("General method");}
void Foo(Bar value) {Console.WriteLine("Specialized method");}

Here, if the compiler knows the types at compile, it will pick the most specific:

Bar bar = new Bar();
Foo(bar); // uses the specialized method

However....

void Test<TSomething>(TSomething value) {
    Foo(value);
}

will use Foo<T> even for TSomething=Bar, as this is burned in at compile-time.

One other approach is to use type-testing within a generic method - however, this is usually a poor idea, and isn't recommended.

Basically, C# just doesn't want you to work with specializations, except for polymorphism:

class SomeBase { public virtual void Foo() {...}}
class Bar : SomeBase { public override void Foo() {...}}

Here Bar.Foo will always resolve to the correct override.

Assuming you're talking about template specialization as it can be done with C++ templates - a feature like this isn't really available in C#. This is because C# generics aren't processed during the compilation and are more a feature of the runtime.

However, you can achieve similar effect using C# 3.0 extension methods. Here is an example that shows how to add extension method only for MyClass<int> type, which is just like template specialization. Note however, that you can't use this to hide default implementation of the method, because C# compiler always prefers standard methods to extension methods:

class MyClass<T> {
  public int Foo { get { return 10; } }
}
static class MyClassSpecialization {
  public static int Bar(this MyClass<int> cls) {
    return cls.Foo + 20;
  }
}

Now you can write this:

var cls = new MyClass<int>();
cls.Bar();

If you want to have a default case for the method that would be used when no specialization is provided, than I believe writing one generic Bar extension method should do the trick:

  public static int Bar<T>(this MyClass<T> cls) {
    return cls.Foo + 42;
  }

A simpler, shorter and more readable version of what @LionAM proposed (about half of the code size), shown for lerp since this was my actual use case:

public interface ILerp<T> {
    T Lerp( T a, T b, float t );
}

public class Lerp : ILerp<float>, ILerp<double> {
    private static readonly Lerp instance = new();

    public static T Lerp<T>( T a, T b, float t )
      => ( instance as ILerp<T> ?? throw new NotSupportedException() ).Lerp( a, b, t );

    float ILerp<float>.Lerp( float a, float b, float t ) => Mathf.Lerp( a, b, t );
    double ILerp<double>.Lerp( double a, double b, float t ) => Mathd.Lerp( a, b, t );
}

You can then just e.g.

Lerp.Lerp(a, b, t);

in any generic context, or provide the method as a grouped Lerp.lerp method reference matching T(T,T,float) signature.

If ClassCastException is good enough for you, you can of course just use

 => ( (ILerp<T>) instance ).Lerp( a, b, t );

to make the code even shorter/simpler.

If you just want to test if a type is derrived from XYZ, then you can use:

theunknownobject.GetType().IsAssignableFrom(typeof(XYZ));

If so, you can cast "theunknownobject" to XYZ and invoke alternativeFunc() like this:

XYZ xyzObject = (XYZ)theunknownobject; 
xyzObject.alternativeFunc();

Hope this helps.

Related