C#をどのように専門化しますか?
問題を提起します。テンプレートタイプがあり、それが何であるかわかりません。しかし、それがXYZ
あなたが呼び出したいものから派生したものであるかどうかはわかります.alternativeFunc()
。優れた方法は、特殊な関数またはクラスを呼び出してnormalCall
return.normalFunc()
を持ち、派生型のXYZ
呼び出しに他の特殊化を行うこと.alternativeFunc()
です。これはC#でどのように行われますか?
回答:
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
は常に正しいオーバーライドに解決されます。
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;
}
static
メソッドを使用しなくても正常に機能するように見えます。ジェネリック型をとるメソッドだけです。つまり、@ MarcGravellの回答で指摘された問題は、メソッドを特定の「データ」タイプ(/ )にテンプレート化するのではなく、MyClass<T>
/のような引数に基づいてメソッドを「テンプレート化」することで回避されるように見えます。MyClass<int>
T
int
void CallAppropriateBar<T>() { (new MyClass<T>()).Bar(); }
です。
中間クラスと辞書を追加することで、専門化が可能です。
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();
詳細な説明とサンプルについては、最近のブログへのこのリンクとフォローアップを参照してください。
テンプレートの特殊化をシミュレートするパターンも探していました。いくつかの状況で機能する可能性のあるいくつかのアプローチがあります。しかし、ケースはどうですか
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;
(タイプは論理的にのみ知られており、静的には知られていないため)
動的解像度を使用して.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
これは、異なるタイプに対して異なる実装が呼び出されることを示しています。
タイプがXYZから派生したものかどうかをテストするだけの場合は、次を使用できます。
theunknownobject.GetType().IsAssignableFrom(typeof(XYZ));
その場合、「theunknownobject」をXYZにキャストし、次のようにalternativeFunc()を呼び出すことができます。
XYZ xyzObject = (XYZ)theunknownobject;
xyzObject.alternativeFunc();
お役に立てれば。
"c++ template specialization"