C# generics method selection

This method:

static int GenericAlgorithm<T>(T point) where T : IPoint => GetDim(point);

... will always call GetDim<T>(T point). The overload resolution is performed at compile-time, and at that stage there's no other applicable method.

If you want overload resolution to be called at execution time, you'd need to use dynamic typing, e.g.

static int GenericAlgorithm<T>(T point) where T : IPoint => GetDim((dynamic) point);

But it's generally a better idea to use inheritance for this - in your example, obviously you could just have the single method and return point.NumDims. I assume in your real code there's some reason the equivalent is trickier to do, but without more context we can't advise on how to use inheritance to perform the specialization. Those are your options though:

  • Inheritance (preferred) for specialization based on the execution-time type of the target
  • Dynamic typing for execution-time overload resolution

As of C# 8.0 you should be able to provide a default implementation for your interface, rather than requiring the generic method.

interface IPoint {
    int NumDims { get => 0; }
}

Implementing a generic method and overloads per IPoint implementation also violates the Liskov Substitution Principle (the L in SOLID). You would be better to push the algorithm into each IPoint implementation, which means you should only need a single method call:

static int GetDim(IPoint point) => point.NumDims;

Visitor Pattern

as an alternative to dynamic usage, you may want to use a Visitor pattern as below:

interface IPoint
{
    public int NumDims { get; }
    public int Accept(IVisitor visitor);
}

public struct Point2 : IPoint
{
    public int NumDims => 2;

    public int Accept(IVisitor visitor)
    {
        return visitor.Visit(this);
    }
}

public struct Point3 : IPoint
{
    public int NumDims => 3;

    public int Accept(IVisitor visitor)
    {
        return visitor.Visit(this);
    }
}

public class Visitor : IVisitor
{
    public int Visit(Point2 toVisit)
    {
        return toVisit.NumDims;
    }

    public int Visit(Point3 toVisit)
    {
        return toVisit.NumDims;
    }
}

public interface IVisitor<T>
{
    int Visit(T toVisit);
}

public interface IVisitor : IVisitor<Point2>, IVisitor<Point3> { }

class Program
{
    static int GetDim<T>(T point) where T : IPoint => 0;
    static int GetDim(Point2 point) => point.NumDims;
    static int GetDim(Point3 point) => point.NumDims;

    static int GenericAlgorithm<T>(T point) where T : IPoint => point.Accept(new Visitor());

    static void Main(string[] args)
    {
        Point2 p2;
        Point3 p3;
        int d1 = GenericAlgorithm(p2);
        int d2 = GenericAlgorithm(p3);
        Console.WriteLine("{0:d}", d1);        // returns 2
        Console.WriteLine("{0:d}", d2);        // returns 3
    }
}

Tags:

C#

Generics