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> { ... }

したがって、HexGridで、クライアントは、正確に正しいタイプで、デュアルグリッド上のポイントを取得するために呼び出しを行うことができます。

TriPoint dual = hexGrid.GetDualPoint(hexPoint, North);

ここまでは順調ですね; クライアントは、2つのポイントがどのように関連するかについてタイプについて何も知る必要はありません。クライアントが知る必要があるのはHexGrid、メソッドでメソッドGetDualPointがを返すことだけTriPointです。

それ外...

IGridたとえば、sで動作する一般的なアルゴリズムでいっぱいのクラスがあります。

static List<TPoint> CalcShortestPath<TPoint, TDualPoint>(
   IGrid<TPoint, TDualPoint> grid, 
   TPoint start, 
   TPoint goal) 
{...}

HexPointここで、クライアントは突然、aのデュアルポイントがであるという詳細を知る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) 
{...}

もちろん、私が知る限り、これは不可能です。

しかし、どういうわけかこれを模倣するようにデザインを変更する方法はありますか?となることによって

  • 2つのタイプの関係を表現しています
  • 過剰な型引数リストを防ぎます
  • これにより、クライアントは、具象型が実装するインターフェースの型パラメーターを具体的な型がどのように「特殊化」するかについて、あまり深く考える必要がなくなります。

これが実際の問題になる理由を示すために:私のライブラリには、IGrid実際には4つのタイプパラメータがIPointあり、3つあり、両方が潜在的に増加します(最大6と5)。(これらのタイプパラメータのほとんどの間にも同様の関係があります。)

アルゴリズムのジェネリックスの代わりに明示的なオーバーロードは実用的ではありません。とのそれぞれに9つの具体的な実装がIGridありIPointます。一部のアルゴリズムは2種類のグリッドで動作するため、1トンの種類のパラメーターがあります。(多くの関数の宣言は関数本体よりも長くなります!)

自動名前変更中に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

だから、コメントで話した使い捨てのアイデアに基づいて答えを投げるつもりです...

基本的な要点は、「ポイントの二重性のこの概念を伝えるタイプを定義し、それを関連するシグニチャで使用して、コンパイラに必要なヒントを与える」でした。

恐ろしい「タイプは使用法から推測できません」エラーが発生するたびに読む必要があることの1つ:http: //blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part- of-the-signature.aspx

その中で、Messr。Lippertは、制約ではなく、署名のパラメーターのみがこの推論段階でチェックされるという厳しい真実を詳しく説明しています。したがって、ここではもう少し「具体的」にする必要があります。

まず、「二重関係」を定義しましょう。これは、これらの関係を設定する1つの方法であり、(理論的には)無限の種類があります。

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 に答える