微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在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();
   }
}

解决方法

所以,根据我在评论中谈到的一次性想法,会提出一个答案……

基本要点是“定义一种传达这种点对偶概念的类型,并在相关的签名中使用它,以便为编译器提供所需的提示

每当你遇到可怕的“类型无法从使用中推断”错误时,你应该阅读的一件事:
http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx

在那,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();

原文地址:https://www.jb51.cc/csharp/99231.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐