web-dev-qa-db-ja.com

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

C#に特化するにはどうしますか?

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

80
user34537

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つは、型テストwithin汎用メソッドを使用することです-ただし、これは通常貧弱なアイデアであり、推奨されません。

基本的に、C#は、ポリモーフィズムを除いて、特殊化を使用したくないだけです。

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

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

83
Marc Gravell

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;
  }
58
Tomas Petricek

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

Tに特化するために、(たとえば)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();

これを参照してください link 私の最近のブログと詳細な説明とサンプルのフォローアップ。

15
Barend Gehrels

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

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

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

_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にすることはできません。

11
LionAM

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

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

C++に似た効果を得るために通常のコンパイラーを実行した後にコードを挿入する方法については、PostSharpを参照することをお勧めします。

6
GregC

動的解像度を使用して.NET 4+でそれを達成する方法があると思います。

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

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

5
Drolevar

型がXYZから派生しているかどうかだけをテストする場合は、次を使用できます。

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

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

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

お役に立てれば。

0
Jeroen Landheer