在 C# 中设计泛型算法

Designing generic algorithms in C#

我正在编写一些 2D/3D 几何库代码。

在这个例子中,让我们考虑一个 Contain 方法来检查 Line 是否完全在 Box 的集合中。我有每个对象的 2D 和 3D 实现。

方法签名看起来像这样:

这两种方法非常相似,唯一的区别是我们使用的是 (Point2, Line2, Box2)(Point3, Line3, Box3) 类型集。作为参考,我的 PointLine 类 是结构。

所以我尝试了如下所示的方法:

static bool Contains<TB, TL, TP>(List<TB> boxes, TL line) 
    where TB: IAxisAlignedBox
    where TL: ILineSegment
    where TP: IPoint

这个的主要问题是我在内部调用另一个函数,以下之一:

正如我在另一个问题中向我解释的那样,这在 C# 中不起作用,因为它只会调用 TP Intersect(TB box, TL line),这不是专门的。

所以我的问题是......我应该如何在不使用 dynamic 的情况下通用地编写此算法?

恐怕 C# 泛型不能那样工作。没有 duck-typing,并且您实际上并没有为每个类型参数获得不同的泛型类型。所有类型解析都发生在编译时,并不关心类型参数的实际类型。要获得 运行 时间类型分辨率,您需要使用 dynamic,尽其所能。

备选方案在很大程度上取决于您的要求。例如,如果您完全控制可以在 Contains 方法中使用的类型,您可以进行自己的类型解析:

if (box is Box2 box2 && line is Line2 line2) return Contains(box2, line2);
else if (box is Box3 box3 && line is Line3 line3) return Contains(box3, line3);

这可能是您可以做的最快的事情(除了首先针对每种情况进行特定实施之外)。如果您正在寻找维护较少的东西,您可以使用委托:

static bool ContainsImpl<TB, TL, TP>(List<TB> boxes, TL line, Func<TB, TL, bool> intersect)
{
  DoAllTheStuff();
  if (intersect(box, line)) ...
}

public static Contains(List<Box2> boxes, Line2 line)=> ContainsImpl(boxes, line, Intersect);
public static Contains(List<Box3> boxes, Line3 line)=> ContainsImpl(boxes, line, Intersect);

泛型方法包含执行 Contains 的通用逻辑,并根据实际类型委托所有特定内容(您的库的任何用户都会使用非泛型方法)。主要好处是您可以通过这种方式进行完整的类型检查——您不会不小心忘记了您添加的新类型的模式匹配案例。不过,考虑到 intersect 方法的简单性,这可能不值得委托的开销。您需要考虑图书馆的用户。

在某些情况下,反转所有内容并使用拉模型(例如通过 IEnumerable<TB>)而不是推模型是值得的。但同样,所有这些都是您必须根据您的实际要求进行的调用。当然,您始终可以使用代码生成 - 有时在 C# 中没有一种表示模式的好方法,而 T4 模板之类的东西可以提供很大帮助。

一般来说,C# 泛型是泛型。如果泛型类型约束不足以满足 Contains 需要做的事情,那么将其设为泛型就没有意义了。您仍然可以使用它来避免不必要的代码重复,它非常适合 运行 时间生成的类型和应用委托,但如果没有显式转换(或 dynamic ).它们不是 C++ 模板,也不会将 C# 更改为 Python 或 Scala。