在C#中表达类型关系并避免使用长类型参数列表
我有这种情况(急剧简化):
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> { ... }
因此,在HexGrid
,客户可以拨打电话在双重网格上获得一个点,并使用正确的类型:
TriPoint dual = hexGrid.GetDualPoint(hexPoint, North);
到现在为止还挺好; 客户端并不需要了解两点是如何关联的类型什么的,她需要知道的是,在HexGrid
方法GetDualPoint
返回一个TriPoint
。
除...
我有一个充满通用算法的类, IGrid
进行操作,例如:
static List<TPoint> CalcShortestPath<TPoint, TDualPoint>(
IGrid<TPoint, TDualPoint> grid,
TPoint start,
TPoint goal)
{...}
现在,客户端突然不得不知道HexPoint
的双重点是HexPoint
的TriPoint
,我们需要将它指定为类型参数列表的一部分,即使它对此算法不严格影响:
static List<TPoint> CalcShortestPath<TPoint, *>(
IGrid<TPoint, *> grid,
TPoint start,
TPoint goal)
{...}
理想情况下,我想使DualPoint成为IPoint
类型的“属性”,以便HexPoint.DualPoint
是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
和IPoint
有9个具体的实现。 一些算法在两种类型的网格上运行,因此具有一吨类型参数。 (许多函数的声明比函数体更长!)
当我的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();
}
}
所以,要根据我在评论中提到的一次性想法提出答案......
基本要点是“定义一种传达这种点对偶概念的类型,并将其用于相关签名中,以便为编译器提供所需的提示”
有一件事你应该阅读,只要你点击可怕的“类型不能推断使用”错误:http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-的最signature.aspx
在此,梅斯先生。 Lippert阐明了严酷的事实,即在这个推论阶段只有签名的参数被检查,而不是约束。 所以我们必须在这里做一个更具体的“特定”。
首先,让我们来定义我们的“二元关系” - 我应该注意到这是建立这些关系的一种方式 ,理论上存在着它们的无限多样性。
public interface IDual<TPoint, TDualPoint>
where TPoint: IPoint<TPoint>, IDual<TPoint, TDualPoint>
where TDualPoint: IPoint<TDualPoint>, IDual<TDualPoint, TPoint>
{}
现在我们回过头来改进我们现有的签名:
public interface IPoint<TPoint>
where TPoint:IPoint<TPoint>
{}
class TriPoint : IPoint<TriPoint>, IDual<TriPoint,HexPoint>
{}
class HexPoint : IPoint<HexPoint>, IDual<HexPoint,TriPoint>
{
// Normally you would rotate the point
public HexPoint Rotate240(){ return new HexPoint();}
}
同样在“次要类型”上,网格:
interface IGrid<TPoint, TDualPoint>
where TPoint: IPoint<TPoint>, IDual<TPoint, TDualPoint>
where TDualPoint : IPoint<TDualPoint>, IDual<TDualPoint, TPoint>
{
TDualPoint GetDualPoint(TPoint point);
}
class HexGrid : IGrid<HexPoint, TriPoint>
{
public TriPoint GetDualPoint(HexPoint point)
{
return new TriPoint();
}
}
class TriGrid : IGrid<TriPoint, HexPoint>
{
public HexPoint GetDualPoint(TriPoint point)
{
return new HexPoint();
}
}
最后在我们的实用方法上:
static class Algorithms
{
public static IEnumerable<TPoint> TransformShape<TPoint, TDualPoint>(
IEnumerable<IDual<TPoint, TDualPoint>> shape,
Func<TPoint, TPoint> transform)
where TPoint : IPoint<TPoint>, IDual<TPoint, TDualPoint>
where TDualPoint : IPoint<TDualPoint>, IDual<TDualPoint, TPoint>
{
return
from TPoint point in shape
select transform(point);
}
public static IEnumerable<TPoint> TransformShape<TPoint, TDualPoint>(
IGrid<TPoint, TDualPoint> grid,
IEnumerable<IDual<TPoint, TDualPoint>> shape,
Func<TPoint, TPoint> transform)
where TPoint : IPoint<TPoint>, IDual<TPoint, TDualPoint>
where TDualPoint : IPoint<TDualPoint>, IDual<TDualPoint, TPoint>
{
return
from TPoint point in shape
//where transform(point) is in grid
select transform(point);
}
}
注意方法上的签名 - 我们在说“嗨,我们给你的这个列表,它绝对有双重点”,这就是允许代码这样的代码:
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();
//Did not compile, but does now!
var rotatedShape3 = Algorithms
.TransformShape(
hexPointShape,
point => point.Rotate240())
.ToList();
我曾经遇到一个泛型重载的情况,我有一组5个泛型接口,每个泛型接口的每个实现都根据它们进行参数化。 理论上这是一个很棒的设计,因为它意味着所有的方法参数和返回类型都是静态检查的。
实际上,在设计一段时间后,意识到任何将这些接口作为参数的方法都必须指定所有的类型参数,我决定简单地使这些接口不是通用的,并且使用运行时转换为方法参数,而不是让编译器强制执行。
我会建议简化设计 - 最多可能从接口中删除所有类型的参数。
一种可能的解决方案,取决于你想要定义的算法的种类,可能是定义更多的接口,这些接口需要更少的类型参数,并且交换暴露更少的方法。
例如:
interface IPoint
{
int X {get;}
int Y {get;}
// Maybe you do not need that one.
IPoint Translate(IPoint dual);
}
interface IPoint<TPoint> : IPoint
where TPoint : IPoint<TPoint>
{
new TPoint Translate(TPoint dual);
}
现在,您可以定义一个算法,该算法在没有关于双点类型的信息泄漏的情况下采用IPoint
。 不过要注意的是,对于同一个事物使用通用和非通用接口会使设计更加复杂。
如果没有关于真实界面的更多信息,以及您需要如何使用它,我不知道要提出什么精确的修改。
不要忘记,你应该平衡实现的复杂性和可读性 - 如果即使你在重写方法类型参数时遇到困难,那么也要考虑那些会使用你的对象而不写它们的人!
链接地址: http://www.djcxy.com/p/70185.html上一篇: Expressing type relationships and avoiding long type parameter lists in C#
下一篇: Sudoku solve method