简体   繁体   中英

C# generics method selection

I'm trying to write generic algorithms in C# that can work with geometric entities of different dimension.

In the following contrived example I have Point2 and Point3 , both implementing a simple IPoint interface.

Now I have a function GenericAlgorithm that calls a function GetDim . There are multiple definitions of this function based on the type. There is also a fall-back function that is defined for anything that implements IPoint .

I initially expected the output of the following program to be 2, 3. However, it is 0, 0.

interface IPoint {
    public int NumDims { get; } 
}

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

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

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 => GetDim(point);

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

OK, so for some reason the concrete type information is lost in GenericAlgorithm . I don't fully understand why this happens, but fine. If I can't do it this way, what other alternatives do I have?

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, eg

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
    }
}

为什么不在类和接口中定义GetDim 函数呢?其实不需要定义GetDim 函数,使用NumDims 属性即可。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM