ジェネリック型の特定のインスタンスによってC#メソッドをオーバーロードするにはどうすればよいですか
-
03-07-2019 - |
質問
C ++のバックグラウンドから来て、ジェネリック型の特定のインスタンスに基づいてオーバーロードを伴うスナッグに遭遇しました。 Foo<T>
クラスのコードのインスタンスが一度しか生成されないため、以下は機能しません。したがって、Method
内では、this
のタイプはFoo<A>
またはFoo<B>
ではなく、単に<=>です。私が望んでいたように。 C ++では、テンプレートを一意の型としてインスタンス化することに慣れています。
using System.Collections.Generic;
class A
{
// Concrete class
}
class B
{
// Concrete class
}
class Bar
{
public void OverloadedMethod(Foo<A> a) {} // do some A related stuff
public void OverloadedMethod(Foo<B> b) {} // do some B related stuff
public void OverloadedMethod(OtherFoo of) {} // do some other stuff
public void VisitFoo(FooBase fb) { fb.Method(this); }
}
abstract class FooBase
{
public abstract void Method(Bar b);
}
class Foo<T> : FooBase
{
// Class that deals with As and Bs in an identical fashion.
public override void Method(Bar b)
{
// Doesn't compile here
b.OverloadedMethod(this);
}
}
class OtherFoo : FooBase
{
public override void Method(Bar b)
{
b.OverloadedMethod(this);
}
}
class Program
{
static void Main(string[] args)
{
List<FooBase> ListOfFoos = new List<FooBase>();
ListOfFoos.Add(new OtherFoo());
ListOfFoos.Add(new Foo<A>());
ListOfFoos.Add(new Foo<B>());
Bar b = new Bar();
foreach (FooBase fb in ListOfFoos)
b.VisitFoo(fb);
// Hopefully call each of the Bar::Overloaded methods
}
}
このようなものをC#で動作させる方法はありますか? Fooのコードを、使用するタイプごとに個別のクラスとして複製する必要はありません。
編集: うまくいけば、これが少し明確になります。
解決
これは静的な場合に機能します。インスタンス関数の処理はもう少し複雑になります。この Jonからの投稿Skeet は、インスタンスメソッドを処理する合理的な方法を提供する場合があります。
class Program
{
static void Main(string[] args)
{
var testA = new Foo<A>();
testA.Method();
var testB = new Foo<B>();
testB.Method();
Console.ReadLine();
var testString = new Foo<string>(); //Fails
testString.Method();
Console.ReadLine();
}
}
class A { }
class B { }
class Bar
{
public static void OverloadedMethod(Foo<A> a)
{
Console.WriteLine("A");
}
public static void OverloadedMethod(Foo<B> b)
{
Console.WriteLine("B");
}
}
class Foo<T>
{
static Foo()
{
overloaded = (Action<Foo<T>>)Delegate.CreateDelegate(typeof(Action<Foo<T>>), typeof(Bar).GetMethod("OverloadedMethod", new Type[] { typeof(Foo<T>) }));
}
public void Method()
{
overloaded(this);
}
private static readonly Action<Foo<T>> overloaded;
}
他のヒント
現在、問題を実証する完全に完全なコードがあります。 OPへの注意:コードを投稿する前にコンパイルしてみてください。これまでのところ、私がしなければならないことがたくさんありました。他の人ができる限り簡単にできるようにするのは良いことです。また、余分なビットも削除しました。 OtherFooはここではあまり関係がなく、FooBaseも関係ありません。
class A {}
class B {}
class Bar
{
public static void OverloadedMethod(Foo<A> a) { }
public static void OverloadedMethod(Foo<B> b) { }
}
class Foo<T>
{
// Class that deals with As and Bs in an identical fashion.
public void Method()
{
// Doesn't compile here
Bar.OverloadedMethod(this);
}
}
はい、これはコンパイルされません。正確に何を期待していましたか?オーバーロードの解決は、実行時ではなくコンパイル時に実行されることに注意してください。 fallen888が言うように、適切なオーバーロードメソッドをキャストして呼び出すことができますが、2つのオーバーロードのうち、コンパイラーが選択するのはどちらですか? Foo<string>
またはFoo<A>
ではなくFoo<B>
で何をしたいですか?
これはすべて、.NETジェネリックが実際にC ++テンプレートとは大幅に異なることを実証するためのものです...もちろん
試したことはありませんが、A <!> amp; B訪問可能(非周期的な訪問者パターンなど)。
編集:試行中にこれを完了できるかどうかわかりません。これを機能させるためにあらゆる種類のトリックを試しましたが、コンパイルすることはできません。私ができる最善の方法は、Genericクラスの外でメソッド呼び出しをプルすることです。メソッド呼び出しが外部の場合、ジェネリックでTを具体的に定義できます。ただし、メソッド内では、コンパイル時にコンパイラーはTが何であるかを知らないため、どのオーバーロードメソッドを呼び出すかを知りません。これを回避する唯一の方法は、スイッチを使用してTのタイプを判別し、呼び出すオーバーロードを手動で指定することです。
できることはこれです。これはあなたが望んでいることとは異なりますが、同様の効果に使用できます:
class Stuff<T>
{
public T value { get; set; }
}
class Program
{
static void DummyFunc(Stuff<int> inst)
{
Console.WriteLine("Stuff<int>: {0}", inst.value.ToString());
}
static void DummyFunc(Stuff<string> inst)
{
Console.WriteLine("Stuff<string>: {0}", inst.value);
}
static void DummyFunc(int value)
{
Console.WriteLine("int: {0}", value.ToString());
}
static void DummyFunc(string value)
{
Console.WriteLine("string: {0}", value);
}
static void Main(string[] args)
{
var a = new Stuff<string>();
a.value = "HelloWorld";
var b = new Stuff<int>();
b.value = 1;
var c = "HelloWorld";
var d = 1;
DummyFunc(a);
DummyFunc(b);
DummyFunc(c);
DummyFunc(d);
}
}
そして出力を得た:
Stuff<string>: HelloWorld
Stuff<int>: 1
string: HelloWorld
int: 1
2つの参照ジェネリッククラス(1つはintと1つは文字列)と2つの参照通常型(1つはintと1つは文字列)を参照する4つのオーバーロード関数があり、すべて正常に動作します...あとですか?
編集:問題はオーバーロードされたメソッドの呼び出しにあるようではなく、リスト内のすべてのアイテムを最初のものと同じタイプに変換しようとしているforeachに関係しています。オーバーロードされたメソッド。その正確な定義に準拠していない最初の項目は、コンパイルに失敗します。
これを行うためのより簡単な方法を見つけたいと思っていましたが、今のところこれを使用しています:
Foo<T>
クラスをこれらのクラスに置き換えます:
abstract class Foo<T> : FooBase
{
// Class that deals with As and Bs in an identical fashion.
}
class Foo_A : Foo<A>
{
public override void Method(Bar b)
{
b.OverloadedMethod(this);
}
}
class Foo_B : Foo<B>
{
public override void Method(Bar b)
{
// Doesn't compile here
b.OverloadedMethod(this);
}
}
インスタンス化を
に変更しますList<FooBase> ListOfFoos = new List<FooBase>();
ListOfFoos.Add(new OtherFoo());
ListOfFoos.Add(new Foo_A());
ListOfFoos.Add(new Foo_B());
これでは、少なくとも<=>のコードを複製する必要はなく、コンストラクタを転送するだけです。