C#ジェネリックメソッドの選択


9

さまざまな次元の幾何学的エンティティを処理できる汎用アルゴリズムをC#で記述しようとしています。

次の不自然な例ではPoint2、とを使用していますがPoint3、どちらも単純なIPointインターフェースを実装しています。

これで、関数GenericAlgorithmを呼び出す関数ができましたGetDim。タイプに基づいて、この関数には複数の定義があります。を実装するものに対して定義されているフォールバック関数もありますIPoint

最初は、次のプログラムの出力が2、3であると予想していましたが、0、0です。

interface IPoint {
    public int NumDims { get; } 
}

public struct Point2 : IPoint {
    public int NumDims => 2;
}

public struct Point3 : IPoint {
    public int NumDims => 3;
}

class Program
{
    static int GetDim<T>(T point) where T: IPoint => 0;
    static int GetDim(Point2 point) => point.NumDims;
    static int GetDim(Point3 point) => point.NumDims;

    static int GenericAlgorithm<T>(T point) where T : IPoint => GetDim(point);

    static void Main(string[] args)
    {
        Point2 p2;
        Point3 p3;
        int d1 = GenericAlgorithm(p2);
        int d2 = GenericAlgorithm(p3);
        Console.WriteLine("{0:d}", d1);        // returns 0 !!
        Console.WriteLine("{0:d}", d2);        // returns 0 !!
    }
}

OK、そのため、何らかの理由で具体的な型情報がで失われGenericAlgorithmます。なぜこれが起こるのか完全にはわかりませんが、問題ありません。この方法でそれができない場合、他にどのような選択肢がありますか?


2
「フォールバック機能もあります」正確には、これの目的は何ですか?インターフェイスを実装する重要な点は、NumDimsプロパティが利用可能であることを保証することです。なぜそれを無視しているのですか?
John Wu、

したがって、基本的にはコンパイルされます。最初は、実行時にJITコンパイラーが専用の実装を見つけられない場合GetDim(つまり、aを渡すPoint4GetDim<Point4>存在しない場合)は、フォールバック関数が必要であると考えました。ただし、コンパイラーが特別な実装を探すのに煩わしいとは思われません。
mohamedmoussa

1
@woggy:これは、「コンパイラーが特別な実装を探すのに煩わしいとは思えない」と言うのですが、これはデザイナーや実装者の面倒な問題のようです。そうではありません。ジェネリックが.NETでどのように表現されるかが問題です。これは、C ++でのテンプレート化と同じ種類の特殊化ではありません。ジェネリックメソッドは、型引数ごとに個別にコンパイルされるのではなく、一度コンパイルされます。確かに、これには長所と短所がありますが、「嫌悪感」の問題ではありません。
Jon Skeet

@jonskeet謝罪私の言語の選択が悪かった場合、私が考慮していない複雑さがここにあると確信しています。私の理解では、コンパイラーは参照型の個別の関数をコンパイルしませんでしたが、値型/構造体はコンパイルしましたが、それは正しいですか?
mohamedmoussa

@woggy:これはJITコンパイラーです。これはC#コンパイラーとはまったく別の問題であり、オーバーロードの解決を実行するのはC#コンパイラーです。ジェネリックメソッドのILは1回だけ生成されます-専門化ごとに1回は生成されません。
Jon Skeet

回答:


10

この方法:

static int GenericAlgorithm<T>(T point) where T : IPoint => GetDim(point);

...は常に呼び出しますGetDim<T>(T point)。オーバーロードの解決はコンパイル時に実行されますで、その段階では他に適用可能なメソッドはありません。

実行時にオーバーロード解決を呼び出したい場合は、動的型付けを使用する必要があります。例:

static int GenericAlgorithm<T>(T point) where T : IPoint => GetDim((dynamic) point);

ただし、これには継承を使用する方が一般的には良い方法point.NumDimsです。この例では、明らかに単一のメソッドとreturnだけを使用できます。私はあなたの実際のコードで同等のことを行うのが難しい理由があると思いますが、これ以上のコンテキストがなければ、継承を使用して特殊化を実行する方法についてアドバイスすることはできません。これらはあなたのオプションです:

  • ターゲットの実行時タイプに基づく特殊化の継承(推奨)
  • 実行時の過負荷を解決するための動的型付け

実際の状況は、私が持っているAxisAlignedBoundingBox2AxisAlignedBoundingBox3。私は、ボックスのコレクションにまたは(ボックスのタイプに依存する)が含まれているかContainsどうかを判断するために使用される静的メソッドがあります。2つのタイプ間のアルゴリズムロジックは、次元数が異なることを除いて、まったく同じです。正しいタイプに特化する必要がある内部への呼び出しもあります。仮想関数呼び出し/動的を避けたいので、ジェネリックスを使用しています...もちろん、コードをコピーして貼り付けて次に進むことができます。Line2Line3Intersect
mohamedmoussa

1
@woggy:説明だけからそれを視覚化することは非常に困難です。継承を使用してこれを行うためのヘルプが必要な場合は、最小限の完全な例で新しい質問を作成することをお勧めします。
Jon Skeet

わかりました、そうします。良い例を提供していないようなので、今のところこの回答を受け入れます。
mohamedmoussa

6

C#8.0以降では、ジェネリックメソッドを要求するのではなく、インターフェイスのデフォルト実装を提供できるはずです。

interface IPoint {
    int NumDims { get => 0; }
}

ジェネリックメソッドの実装と実装ごとのオーバーロードIPointも、リスコフ代入原理(SOLIDのL)に違反します。アルゴリズムを各IPoint実装にプッシュすることをお勧めします。つまり、必要なのは単一のメソッド呼び出しのみです。

static int GetDim(IPoint point) => point.NumDims;

3

訪問者パターン

dynamic使用法の代わりに、以下のビジターパターンを使用することもできます。

interface IPoint
{
    public int NumDims { get; }
    public int Accept(IVisitor visitor);
}

public struct Point2 : IPoint
{
    public int NumDims => 2;

    public int Accept(IVisitor visitor)
    {
        return visitor.Visit(this);
    }
}

public struct Point3 : IPoint
{
    public int NumDims => 3;

    public int Accept(IVisitor visitor)
    {
        return visitor.Visit(this);
    }
}

public class Visitor : IVisitor
{
    public int Visit(Point2 toVisit)
    {
        return toVisit.NumDims;
    }

    public int Visit(Point3 toVisit)
    {
        return toVisit.NumDims;
    }
}

public interface IVisitor<T>
{
    int Visit(T toVisit);
}

public interface IVisitor : IVisitor<Point2>, IVisitor<Point3> { }

class Program
{
    static int GetDim<T>(T point) where T : IPoint => 0;
    static int GetDim(Point2 point) => point.NumDims;
    static int GetDim(Point3 point) => point.NumDims;

    static int GenericAlgorithm<T>(T point) where T : IPoint => point.Accept(new Visitor());

    static void Main(string[] args)
    {
        Point2 p2;
        Point3 p3;
        int d1 = GenericAlgorithm(p2);
        int d2 = GenericAlgorithm(p3);
        Console.WriteLine("{0:d}", d1);        // returns 2
        Console.WriteLine("{0:d}", d2);        // returns 3
    }
}

1

GetDim関数をクラスとインターフェイスで定義しないのですか?実際、GetDim関数を定義する必要はありません。プロパティNumDimsを使用するだけです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.