C#泛型方法选择


9

我试图用C#编写可与不同维度的几何实体一起使用的通用算法。

在以下人为设计的示例中,我Point2和和Point3都实现了一个简单的IPoint接口。

现在,我有一个GenericAlgorithm调用函数的函数GetDim。根据类型,此函数有多个定义。还为所有实现的东西定义了一个后备功能IPoint

我最初希望以下程序的输出为2、3。但是,它的值为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 !!
    }
}

好的,因此由于某种原因,具体类型信息丢失了GenericAlgorithm。我不完全理解为什么会这样,但是很好。如果我不能这样做,我还有什么其他选择?


2
“还有一个后备功能”,这的目的是什么?实现接口的全部目的是确保该NumDims属性可用。为什么在某些情况下会忽略它?
John Wu

因此,基本上可以编译。最初,我认为如果JIT编译器在运行时找不到专用的实现GetDim(即,我通过a Point4GetDim<Point4>不存在),则需要回退功能。但是,似乎编译器不会费心寻找专门的实现。
mohamedmoussa

1
@woggy:您说“似乎没有编译器会去寻找专门的实现”,就好像设计师和实现者的懒惰一样。不是。这是.NET中泛型的表示方式的问题。它只是与C ++中的模板不同。泛型方法不会针对每个类型参数单独编译-只会编译一次。当然,这有优点也有缺点,但这不是“生硬”的问题。
乔恩·斯基特

@jonskeet抱歉,如果我的语言选择不佳,我敢肯定这里没有考虑到复杂性。我的理解是,编译器不会为引用类型编译单独的函数,而是为值类型/结构编译,是正确的吗?
mohamedmoussa

@woggy:那是JIT编译器,它与C#编译器是完全独立的事情-它是执行重载解析的C#编译器。通用方法的IL仅生成一次-每个专门化不会生成一次。
Jon Skeet

Answers:


10

该方法:

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

...会一直打电话GetDim<T>(T point)。重载解析是在编译时执行的,在那个阶段,没有其他适用的方法。

如果要在执行时调用重载解决方案,则需要使用动态类型,例如

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

但这通常是一个更好的主意-在您的示例中,显然您可以只使用单个方法并返回point.NumDims。我认为在您的真实代码中,存在某些原因,使得等效操作比较棘手,但是如果没有更多上下文,我们就无法建议如何使用继承来执行专业化。这些是您的选择:

  • 根据目标的执行时间类型进行继承(首选)进行专业化
  • 动态类型用于执行时过载解析

实际情况是我有一个AxisAlignedBoundingBox2AxisAlignedBoundingBox3。我有一个Contains静态方法,该方法用于确定一盒子是否包含Line2Line3(取决于盒子的类型)。两种类型之间的算法逻辑完全相同,只是维数不同。Intersect内部也有对的调用,这些调用需要专门用于正确的类型。我想避免虚函数调用/动态,这就是为什么我要使用泛型...当然,我可以复制/粘贴代码然后继续。
mohamedmoussa

1
@woggy:仅凭描述很难将其形象化。如果您希望获得使用继承进行此操作的帮助,建议您使用一个简单但完整的示例创建一个新问题。
乔恩·斯基特

好的,会的,我现在将接受这个答案,因为似乎我没有提供一个很好的例子。
mohamedmoussa

6

从C#8.0开始,您应该能够为您的接口提供默认的实现,而不需要常规方法。

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

实现通用方法和每个IPoint实现的重载也违反了Liskov替换原理(SOLID中的L)。您最好将算法推入每个IPoint实现中,这意味着您只需要一个方法调用即可:

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

3

访客模式

作为使用的替代方法dynamic,您可能希望使用如下的“ 访客”模式

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

1

为什么不在类和接口中定义GetDim函数呢?实际上,您不需要定义GetDim函数,只需使用属性NumDims。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.