在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); 到现在为止还挺好;客户端不需要知道关于两点如何关联的类型,她需要知道的是,方法GetDualPoint返回TriPoint. 除了… 我有一个充满通用算法的类,可以在IGrids上运行,例如: static List<TPoint> CalcShortestPath<TPoint,TDualPoint>( IGrid<TPoint,TDualPoint> grid,TPoint start,TPoint goal) {...} 现在,客户端突然必须知道HexPoint的双点是TriPoint的细节,我们需要将其指定为类型参数列表的一部分,即使它对此算法并不严格: static List<TPoint> CalcShortestPath<TPoint,*>( IGrid<TPoint,*> grid,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 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 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,TPoint> { return from TPoint point in shape select transform(point); } public static IEnumerable<TPoint> TransformShape<TPoint,TDualPoint>( IGrid<TPoint,IEnumerable<TPoint> shape,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(); } } 解决方法
所以,根据我在评论中谈到的一次性想法,会提出一个答案……
基本要点是“定义一种传达这种点对偶概念的类型,并在相关的签名中使用它,以便为编译器提供所需的提示” 每当你遇到可怕的“类型无法从使用中推断”错误时,你应该阅读的一件事: 在那,Messr. 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>,TDualPoint> where TDualPoint : IPoint<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,TPoint> transform) where TPoint : IPoint<TPoint>,TDualPoint> where TDualPoint : IPoint<TDualPoint>,IEnumerable<IDual<TPoint,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,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(); (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |