7

我有这种情况(大大简化):

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

除了...

我有一个充满通用算法的类,它们对IGrids 进行操作,例如:

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

2 回答 2

2

我曾经遇到过泛型重载的情况,我有一组 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没有关于双点类型泄漏的信息的情况下采用。但是请注意,为同一事物使用通用和非通用接口会使设计更加复杂。

如果没有更多关于真实接口的信息,以及你需要如何使用它,我真的不知道要建议什么精确的修改。

不要忘记你应该平衡实现的复杂性和可读性——即使你在重写方法类型参数时也遇到了麻烦,那么想想那些会在没有编写对象的情况下使用你的对象的人!

于 2013-03-21T12:50:50.890 回答
2

所以,要根据我在评论中谈到的一次性想法抛出一个答案......

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

每当您遇到可怕的“无法从使用中推断类型”错误时,您应该阅读一件事:http: //blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part- of-the-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();
于 2013-03-22T16:15:26.007 回答