C#でテンプレートの特殊化を行う方法


84

C#をどのように専門化しますか?

問題を提起します。テンプレートタイプがあり、それが何であるかわかりません。しかし、それがXYZあなたが呼び出したいものから派生したものであるかどうかはわかります.alternativeFunc()。優れた方法は、特殊な関数またはクラスを呼び出してnormalCallreturn.normalFunc()を持ち、派生型のXYZ呼び出しに他の特殊化を行うこと.alternativeFunc()です。これはC#でどのように行われますか?

回答:


86

C#では、特殊化に最も近いのは、より具体的なオーバーロードを使用することです。ただし、これは脆弱であり、考えられるすべての使用法を網羅しているわけではありません。例えば:

void Foo<T>(T value) {Console.WriteLine("General method");}
void Foo(Bar value) {Console.WriteLine("Specialized method");}

ここで、コンパイラがコンパイル時に型を知っている場合、最も具体的なものを選択します。

Bar bar = new Bar();
Foo(bar); // uses the specialized method

しかしながら....

void Test<TSomething>(TSomething value) {
    Foo(value);
}

これはコンパイル時にバーンインされるFoo<T>ためTSomething=Bar、に対しても使用されます。

もう1つのアプローチは、ジェネリックメソッド内で型テストを使用することです。ただし、これは通常、お勧めできません。推奨されません。

基本的に、C#は、ポリモーフィズムを除いて、特殊化を使用することを望んでいません。

class SomeBase { public virtual void Foo() {...}}
class Bar : SomeBase { public override void Foo() {...}}

ここでBar.Fooは常に正しいオーバーライドに解決されます。


63

C ++テンプレートで実行できるテンプレートの特殊化について話していると仮定すると、このような機能はC#では実際には利用できません。これは、C#ジェネリックがコンパイル中に処理されず、ランタイムの機能であるためです。

ただし、C#3.0拡張メソッドを使用して同様の効果を実現できます。MyClass<int>これは、テンプレートの特殊化と同じように、型に対してのみ拡張メソッドを追加する方法を示す例です。ただし、C#コンパイラは常に拡張メソッドよりも標準メソッドを優先するため、これを使用してメソッドのデフォルトの実装を非表示にすることはできないことに注意してください。

class MyClass<T> {
  public int Foo { get { return 10; } }
}
static class MyClassSpecialization {
  public static int Bar(this MyClass<int> cls) {
    return cls.Foo + 20;
  }
}

今、あなたはこれを書くことができます:

var cls = new MyClass<int>();
cls.Bar();

特殊化が提供されていないときに使用されるメソッドのデフォルトのケースが必要な場合は、1つのジェネリックBar拡張メソッドを作成することでうまくいくと思います。

  public static int Bar<T>(this MyClass<T> cls) {
    return cls.Foo + 42;
  }

バー方式VS fooプロパティ...本当に...典型的な分業ように見えるしていません
マルクGravell

2
いいえ、それは典型的な仕様ではありませんが、あなたができる唯一の簡単なことです...(AFAIK)
Tomas Petricek 2009年

3
これは、拡張staticメソッドを使用しなくても正常に機能するように見えます。ジェネリック型をとるメソッドだけです。つまり、@ MarcGravellの回答で指摘された問題は、メソッドを特定の「データ」タイプ(/ )にテンプレート化するのではなく、MyClass<T>/のような引数に基づいてメソッドを「テンプレート化」することで回避されるように見えます。MyClass<int>Tint
スリップD.トンプソン2014

4
追加の制限は、メソッド内などからの間接的な汎用呼び出しでは機能しないことvoid CallAppropriateBar<T>() { (new MyClass<T>()).Bar(); }です。
BartoszKP 2018

18

中間クラスと辞書を追加することで、専門化が可能です。

Tに特化するために、(eg)Applyというメソッドを持つ汎用インターフェースを作成します。インターフェイスが実装されている特定のクラスについて、そのクラスに固有のメソッドApplyを定義します。この中間クラスは、トレイトクラスと呼ばれます。

そのトレイトクラスは、汎用メソッドの呼び出しでパラメーターとして指定できます。これにより、(もちろん)常に適切な実装が行われます。

手動で指定する代わりに、トレイトクラスをグローバルに保存することもできIDictionary<System.Type, object>ます。それからそれを調べて出来上がり、あなたはそこに本当の専門性を持っています。

便利な場合は、拡張メソッドで公開できます。

class MyClass<T>
{
    public string Foo() { return "MyClass"; }
}

interface BaseTraits<T>
{
    string Apply(T cls);
}

class IntTraits : BaseTraits<MyClass<int>>
{
    public string Apply(MyClass<int> cls)
    {
        return cls.Foo() + " i";
    }
}

class DoubleTraits : BaseTraits<MyClass<double>>
{
    public string Apply(MyClass<double> cls)
    {
        return cls.Foo() + " d";
    }
}

// Somewhere in a (static) class:
public static IDictionary<Type, object> register;
register = new Dictionary<Type, object>();
register[typeof(MyClass<int>)] = new IntTraits();
register[typeof(MyClass<double>)] = new DoubleTraits();

public static string Bar<T>(this T obj)
{
    BaseTraits<T> traits = register[typeof(T)] as BaseTraits<T>;
    return traits.Apply(obj);
}

var cls1 = new MyClass<int>();
var cls2 = new MyClass<double>();

string id = cls1.Bar();
string dd = cls2.Bar();

詳細な説明とサンプルについては、最近のブログへのこのリンクとフォローアップを参照してください。


これはファクトリパターンであり、ジェネリック医薬品のいくつかの欠点に対処するための適切な方法です
Yaur 2014年

1
@Yaur私には教科書のデコレータパターンのように見えます。
スリップD.トンプソン2014

13

テンプレートの特殊化をシミュレートするパターンも探していました。いくつかの状況で機能する可能性のあるいくつかのアプローチがあります。しかし、ケースはどうですか

static void Add<T>(T value1, T value2)
{
    //add the 2 numeric values
}

たとえば、ステートメントを使用してアクションを選択することができますif (typeof(T) == typeof(int))。ただし、単一の仮想関数呼び出しのオーバーヘッドを使用して、実際のテンプレートの特殊化をシミュレートするためのより良い方法があります。

public interface IMath<T>
{
    T Add(T value1, T value2);
}

public class Math<T> : IMath<T>
{
    public static readonly IMath<T> P = Math.P as IMath<T> ?? new Math<T>();

    //default implementation
    T IMath<T>.Add(T value1, T value2)
    {
        throw new NotSupportedException();    
    }
}

class Math : IMath<int>, IMath<double>
{
    public static Math P = new Math();

    //specialized for int
    int IMath<int>.Add(int value1, int value2)
    {
        return value1 + value2;
    }

    //specialized for double
    double IMath<double>.Add(double value1, double value2)
    {
        return value1 + value2;
    }
}

これで、事前にタイプを知らなくても、書くことができます。

static T Add<T>(T value1, T value2)
{
    return Math<T>.P.Add(value1, value2);
}

private static void Main(string[] args)
{
    var result1 = Add(1, 2);
    var result2 = Add(1.5, 2.5);

    return;
}

実装された型だけでなく派生型に対しても特殊化を呼び出す必要がある場合はIn、インターフェイスのパラメーターを使用できます。ただし、この場合、メソッドの戻り値の型をジェネリック型にすることはできなくなりTます。


これはかなり素晴らしいです、ありがとう。これにより、それぞれが特定のタイプ用に記述された既存のメソッドの束を呼び出すためのジェネリックインターフェイスを作成できましたが、ジェネリックで書き直すことはできませんでした(または少なくとも非常に困難でした)。それは私がいくつかの恐ろしいことをしなければならないように見え始めていてif (type == typeof(int))、それから余分なボクシング/ボックス化解除を伴う一般的なタイプにキャストバックしましたreturn (T)(object)result;(タイプは論理的にのみ知られており、静的には知られていないため)
Andrew Wright

6

提案された回答のいくつかは、実行時型情報を使用しています。コンパイル時のバインドされたメソッド呼び出しよりも本質的に低速です。

コンパイラは、C ++の場合のように特殊化を強制しません。

C ++と同様の効果を実現するために、通常のコンパイラーが実行された後にコードを挿入する方法については、PostSharpを調べることをお勧めします。


4

動的解像度を使用して.NET4 +でそれを実現する方法があると思います。

static class Converter<T>
{
    public static string Convert(T data)
    {
        return Convert((dynamic)data);
    }

    private static string Convert(Int16 data) => $"Int16 {data}";
    private static string Convert(UInt16 data) => $"UInt16 {data}";
    private static string Convert(Int32 data) => $"Int32 {data}";
    private static string Convert(UInt32 data) => $"UInt32 {data}";
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(Converter<Int16>.Convert(-1));
        Console.WriteLine(Converter<UInt16>.Convert(1));
        Console.WriteLine(Converter<Int32>.Convert(-1));
        Console.WriteLine(Converter<UInt32>.Convert(1));
    }
}

出力:

Int16 -1
UInt16 1
Int32 -1
UInt32 1

これは、異なるタイプに対して異なる実装が呼び出されることを示しています。


2
少し泣きたいです。
user2864740 2018年

0

タイプがXYZから派生したものかどうかをテストするだけの場合は、次を使用できます。

theunknownobject.GetType().IsAssignableFrom(typeof(XYZ));

その場合、「theunknownobject」をXYZにキャストし、次のようにalternativeFunc()を呼び出すことができます。

XYZ xyzObject = (XYZ)theunknownobject; 
xyzObject.alternativeFunc();

お役に立てれば。


1
私はC#をあまり知りませんが、あなたを投票した人は誰でもその理由を言うべきです。あなたの答えが何が悪いのか、何かが間違っているのか、私にはわかりません。

どちらかわからない。それは私には十分に有効なようです。必要以上に冗長ですが。
jalf 2009年

3
それは私ではありませんでしたが、それは答えが質問とはまったく無関係だからです。ルックアップ"c++ template specialization"
georgiosd 2012年

これは常に機能するとは限りません。たとえば、Tがboolであるかどうかを確認してから、boolにキャストすることはできません。
コス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.