型C#によるメソッドのオーバーロード
-
05-07-2019 - |
質問
次のことが可能かどうか疑問に思っていました。匿名型(string、int、decimal、customObjectなど)を受け入れるクラスを作成し、Typeに基づいてさまざまな操作を行うメソッドをオーバーロードします。例
class TestClass<T>
{
public void GetName<string>()
{
//do work knowing that the type is a string
}
public string GetName<int>()
{
//do work knowing that the type is an int
}
public string GetName<int>(int addNumber)
{
//do work knowing that the type is an int (overloaded)
}
public string GetName<DateTime>()
{
//do work knowing that the type is a DateTime
}
public string GetName<customObject>()
{
//do work knowing that the type is a customObject type
}
}
これでGetNameメソッドを呼び出すことができ、オブジェクトを初期化したときにすでに型を渡したため、正しいメソッドが見つかり実行されます。
TestClass foo = new TestClass<int>();
//executes the second method because that's the only one with a "int" type
foo.GetName();
これは可能ですか、それとも夢を見ているだけですか?
解決
あなたがしようとしていることは、このように可能です:
class TestClass<T>
{
public string GetName<T>()
{
Type typeOfT = typeof(T);
if(typeOfT == typeof(string))
{
//do string stuff
}
}
}
これは 可能ですが、ジェネリックの目的を打ち負かすようなものです。ジェネリックのポイントは、タイプが重要ではない場合であるため、この場合はジェネリックが適切であるとは思わない。
他のヒント
C#では特殊化はできません。 C#で最も近いものは次のとおりです
public void Example() {
public static void Method<T>(T value) { ... }
public static void Method(int value){ ... }
public static void Method(string) { ... }
}
C#コンパイラは、汎用メソッドよりも非汎用メソッドを優先します。これは、intパラメーターで呼び出すと、intオーバーロードと汎用オーバーロードにバインドされることを意味します。
Example.Method(42); // Method(int)
Example.Method(new Class1()) // Method<T>(T)
メソッドがジェネリックに呼び出されたときに適用されないため、これはあなたを噛みます。その場合、タイプに関係なく汎用オーバーロードにバインドされます。
public void Gotcha<T>(T value) {
Example.Method(value);
}
Gotcha(42); // Still calls Example.Method<T>()
<!> quot; Specialization <!> quot; C#ではC ++のようにはできません。 .NETジェネリックでは、<!> lt; T <!> gt;のジェネリッククラスまたはメソッド。 Tのすべての可能な値で同じでなければなりません。これにより、ランタイムは、TestClass <!> lt; string <!> gt;など、2つの異なる参照タイプの最適化を実行できます。およびTestClass <!> lt; List <!> lt; int <!> gt; <!> gt;は、同じマシン言語コードを共有します。 (異なる値型は個別のマシンコードを取得しますが、それでも特殊化することはできません。)
次のような汎用インターフェイスまたは基本クラスを作成すると役立つ場合があります:
abstract class Base<T> {
public abstract T GetName();
// any common code goes here that does not require specialization
}
そして派生クラスに特化する:
class IntVersion : Base<int> {
public override int GetName() { return 1; }
public int GetName(int addNumber) { ... }
}
class StringVersion : Base<string> {
public override string GetName() { return "foo"; }
}
class DateTimeVersion : Base<DateTime> {
public override DateTime GetName() { return DateTime.Now; }
}
いいえ、これは不可能です。あなたがやろうとしていることは、C ++でのテンプレートの特殊化に似ています。これはC#では(残念ながら)不可能です。
if / elseまたはスイッチをオンにする必要があります
typeof(T)
特殊な実装を呼び出す。
ただし、Tの型を、クラス(参照値)または構造体(値)、または特定のベースクラスのサブクラスのいずれかに制限できます。
public Foo<T> DoBar<T>() where T : FooBase;
c#は、このようなディスパッチをサポートしていません。
そして、これは、メソッドのオーバーロードを行う正しい方法ではありません(Error'TestClass 'は、同じパラメーター型を持つ' GetName 'というメンバーを既に定義しています)<!> lt; <!> gt;メソッドシグネチャの一部ではありません。
クラス拡張メソッドを使用すると効果がありますか?
基本的に、必要なクラスにメソッドを追加して、同じ方法で呼び出すことができます。
namespace ExtensionMethods
{
public static class MyExtensions
{
public static int GetName(this String str)
{
...
}
}
}
使用方法:
myString.GetName();
クラスでタイプ固有の作業を行う必要がある場合、クラスはジェネリックではありません。おそらく、処理するタイプごとに個別のクラスを作成する必要があります。汎用的である適切な理由がある機能がある場合、それを共通の基本クラスに入れることができます。
例:
abstract class TestClass<T>
{
public List<T> Items { get; set; }
// other generic (i.e. non type-specific) code
}
class IntTestClass : TestClass<int>
{
public string GetName()
{
// do work knowing that the type is an int
}
// other code specific to the int case
}
class StringTestClass : TestClass<string>
{
public string GetName()
{
// do work knowing that the type is a string
}
// other code specific to the string case
}
BFreeが述べたように、ifツリー、またはより多くの場合switchステートメントでこれを行うことができますが、ある時点でメソッドを記述して.Netにそれを理解させることができます。時間の経過とともに過負荷になります。
.Netのパフォーマンス面ではかなり安価ですが、解決策はリフレクションがあります:
using System.Reflection;
...
public string DoSomething(object val)
{
// Force the concrete type
var typeArgs = new Type[] { val.GetType() };
// Avoid hard-coding the overloaded method name
string methodName = new Func<string, string>(GetName).Method.Name;
// Use BindingFlags.NonPublic instead of Public, if protected or private
var bindingFlags = BindingFlags.Public | BindingFlags.Instance;
var method = this.GetType().GetMethod(
methodName, bindingFlags, null, typeArgs, null);
string s = (string)method.Invoke(this, new object[] { val });
return s;
}
基本的には、Reflectionフレームワークにswitchステートメントを実行するよう指示しているだけです。