我有这种情况(大大简化):
interface IPoint<TPoint>
where TPoint:IPoint<TPoint>
{
//example method
TPoint Translate(TPoint offset);
}
interface IGrid<TPoint, TDualPoint>
where TPoint:IPoint<T
where TDualPoint:Ipoint
{
TDualPoint GetDualPoint(TPoint point, /* Parameter specifying direction */);
}
这是典型的实现:
class HexPoint : IPoint<HexPoint> { ... }
class TriPoint : IPoint<TriPoint> { ... }
class HexGrid : IGrid<HexPoint, TriPoint> { ... }
class TriGrid : IGrid<TriPoint, HexPoint> { ... }
因此,在 a 上HexGrid
,客户端可以调用以获取双网格上的一个点,并使用完全正确的类型:
TriPoint dual = hexGrid.GetDualPoint(hexPoint, North);
到目前为止,一切都很好; 客户不需要知道这两个点如何关联的类型,她只需要知道在 aHexGrid
上方法GetDualPoint
返回 a TriPoint
。
除了...
我有一个充满通用算法的类,它们对IGrid
s 进行操作,例如:
static List<TPoint> CalcShortestPath<TPoint, TDualPoint>(
IGrid<TPoint, TDualPoint> grid,
TPoint start,
TPoint goal)
{...}
现在,客户端突然必须知道 a 的对偶点是 a 的小细节,HexPoint
我们TriPoint
需要将它指定为类型参数列表的一部分,尽管这对于这个算法并不重要:
static List<TPoint> CalcShortestPath<TPoint, *>(
IGrid<TPoint, *> grid,
TPoint start,
TPoint goal)
{...}
理想情况下,我想让 DualPoint 成为 type 的“属性” IPoint
,所以这HexPoint.DualPoint
就是type TriPoint
。
允许 IGrid 看起来像这样的东西:
interface IGrid<TPoint>
where TPoint:IPoint<TPoint>
//and TPoint has "property" DualPoint where DualPoint implements IPoint...
{
IGrid<TPoint.DualPoint> GetDualGrid();
}
和这样的CalcShortestPath
功能
static List<TPoint> CalcShortestPath<TPoint>(
IGrid<TPoint> grid,
TPoint start,
TPoint goal)
{...}
当然,据我所知,这是不可能的。
但是有没有办法我可以改变我的设计以某种方式模仿这个?以便
- 它表达了两种类型之间的关系
- 它可以防止过多的类型参数列表
- 它可以防止客户过分考虑具体类型如何“专门化”该类型实现的接口的类型参数。
说明为什么这会成为一个真正的问题:在我的库IGrid
中实际上有 4 个类型参数,IPoint
有 3 个,并且两者都可能增加(最多 6 个和 5 个)。(大多数这些类型参数之间存在类似的关系。)
算法的显式重载而不是泛型是不切实际的:每个IGrid
和都有 9 个具体实现IPoint
。一些算法在两种类型的网格上运行,因此具有大量的类型参数。(很多函数的声明都比函数体长!)
当我的 IDE 在自动重命名期间丢弃所有类型参数时,我的精神负担被驱散了,我不得不手动放回所有参数。这不是一项盲目的任务。我的大脑被炸了。
根据@Iridium 的要求,显示类型推断何时失败的示例。显然,下面的代码没有做任何事情;这只是为了说明编译器行为。
using System;
using System.Collections.Generic;
using System.Linq;
public interface IPoint<TPoint, TDualPoint>
where TPoint:IPoint<TPoint, TDualPoint>
where TDualPoint : IPoint<TDualPoint, TPoint>{}
interface IGrid<TPoint, TDualPoint>
where TPoint:IPoint<TPoint, TDualPoint>
where TDualPoint:IPoint<TDualPoint, TPoint>{}
class HexPoint : IPoint<HexPoint, TriPoint>
{
public HexPoint Rotate240(){ return new HexPoint();} //Normally you would rotate the point
}
class TriPoint : IPoint<TriPoint, HexPoint>{}
class HexGrid : IGrid<HexPoint, TriPoint>{}
static class Algorithms
{
public static IEnumerable<TPoint> TransformShape<TPoint, TDualPoint>(
IEnumerable<TPoint> shape,
Func<TPoint, TPoint> transform)
where TPoint : IPoint<TPoint, TDualPoint>
where TDualPoint : IPoint<TDualPoint, TPoint>
{
return
from TPoint point in shape
select transform(point);
}
public static IEnumerable<TPoint> TransformShape<TPoint, TDualPoint>(
IGrid<TPoint, TDualPoint> grid,
IEnumerable<TPoint> shape,
Func<TPoint, TPoint> transform)
where TPoint : IPoint<TPoint, TDualPoint>
where TDualPoint : IPoint<TDualPoint, TPoint>
{
return
from TPoint point in shape
//where transform(point) is in grid
select transform(point);
}
}
class UserCode
{
public static void UserMethod()
{
HexGrid hexGrid = new HexGrid();
List<HexPoint> hexPointShape = new List<HexPoint>(); //Add some items
//Compiles
var rotatedShape1 = Algorithms.TransformShape(
hexGrid,
hexPointShape,
point => point.Rotate240()).ToList();
//Compiles
var rotatedShape2 = Algorithms.TransformShape<HexPoint, TriPoint>(
hexPointShape,
point => point.Rotate240()).ToList();
//Does not compile
var rotatedShape3 = Algorithms.TransformShape(
hexPointShape,
point => point.Rotate240()).ToList();
}
}