7

次のコードを検討してください。

class MyClass
{
}

class MyClass2 : MyClass
{
}

private void Foo(MyClass cl)
{
    //cl is actually MyClass2 instance
    TestGeneric(cl);
}

private void TestGeneric<T>(T val)
{
     //do smth
}

Foo()を呼び出した後、 TestGeneric の T は MyClass2ではなくMyClassになります。valMyClass2インスタンスとして扱うにはどうすればよいですか? 前もって感謝します。

Upd: オブジェクトがMyClass2 ctorを使用して作成されたことを実際には知りませんが、val.GetType() を呼び出すことでこれを推測できるため、単純なMyClass2は機能しません。

4

7 に答える 7

5

ビジターパターンでできます。すべての処理コードを (各メッセージではなく) 単一のハンドラー クラスに配置し、より多くのメッセージ タイプが必要な場合は、ハンドラー メソッドを追加するだけの場合、これは優れたオブジェクト指向アプローチです。

// Your message classes
public class MyClass : IMessage
{
    // Implement acceptance of handler:
    public void AcceptHandler(IMessageHandler handler)
    {
        handler.HandleMessage(this);
    }
}

public class MyClass2 : MyClass
{
     // Nothing more here
}

// Define interface of message
public interface IMessage
{
    void AcceptHandler(IMessageHandler handler)
}

// Define interface of handler
public interface IMessageHandler
{
    // For each type of message, define separate method
    void HandleMessage(MyClass message)
    void HandleMessage(MyClass2 message)
}

// Implemente actual handler implementation
public class MessageHandler : IMessageHandler 
{
    // Main handler method
    public void HandleSomeMessage(MyClass message) // Or it could be IMessage
    {
         // Pass this handler to message. Since message implements AcceptHandler
         // as just passing itself to handler, correct method of handler for MyClass
         // or MyClass2 will be called at runtime.
         message.AcceptHandler(this);
    }

    public void HandleMessage(MyClass message)
    {
         // Implement what do you need to be done for MyClass
    }

    public void HandleMessage(MyClass2 message)
    {
         // Implement what do you need to be done for MyClass2
         // If code of MyClass should be run too, just call 
         // this.HandleMessage((MyClass)message);
    }
}
于 2012-12-01T12:07:10.167 に答える
1

署名は変更できるが、署名は変更できないと仮定するとFoo、次のように実行できます。

private void Foo(MyClass cl)
{
    TestGeneric((dynamic)cl);
}

TestGenericこれにより、コンパイル時ではなく実行時に呼び出されるバージョンが解決され、がそのタイプのTestGeneric<MyClass2>場合に呼び出されます。cl

于 2012-11-29T09:49:51.470 に答える
0

最善の解決策はFoo、タイプ情報を保存できるように、メソッドも汎用に変更することです。あなたはそのようにこれをするべきです:

private void Foo<T>(T cl) where T : MyClass
{
    TestGeneric(cl);
}

そうでなければ、悪いデザインの例があります。簡単な方法は

private void Foo(MyClass cl)
{
    if (cl is MyClass2)
        TestGeneric((MyClass2)cl);
    else
        TestGeneric(cl);
}

リフレクションを使用してより広範なソリューションを実行することもできますが、それは悪いデザインにパッチを当てるためのツールの乱用になります。

以下はリフレクションベースのソリューションですが、実行しなかったので、我慢してエラーの修正を試みてください。

private void Foo(MyClass cl)
{
    Type genMethodType = typeof(TestGenericMethodClass);
    MethodInfo genMethod = genMethodType.GetMethod("TestGeneric");
    MethodInfo methodConstructed = genMethod.MakeGenericMethod(cl.GetType());
    object[] args = new object[] { cl };
    methodConstructed.Invoke(instanceOfTestGenericMethodClass, args);
}

それで

  1. TestGenericメソッドが定義されているクラスのタイプを取得します
  2. Type.GetMethodを使用して、メソッド定義を取得します
  3. 変数の実際のタイプを取得して、clジェネリックメソッドを構築します
  4. MethodInfo.MakeGenericMethodを使用して、特定のタイプのメソッドを作成します
  5. MethodBase.Invokeを使用して、構築されたメソッドを呼び出します

コードは、現在の実装に基づいて異なります(タイプ名、メソッドのアクセシビリティなどによって異なります)。

于 2012-11-29T09:08:38.870 に答える
0

ここでジェネリック メソッドを呼び出したくありません。を入力するとTestGeneric<T>、 が希望どおりであっても、 (または、 に制限を追加しない限り)に対してコードを書くことはできないTため、役に立ちません!MyClass2MyClass2MyClassT

確かに、反射やdynamic.

これを行う最も明白な方法: クラス固有の動作をクラス自体に配置します。

class MyClass
{
    public virtual void Test()
    {
        // Behaviour for MyClass
    }
}

class MyClass2 : MyClass
{
    public override void Test()
    {
        // Behaviour for MyClass2
    }
}

private void Foo(MyClass cl)
{
    cl.Test();
}

次善: 渡されたタイプに応じた分岐コード:

private void Foo(MyClass cl)
{
    if (cl is MyClass2)
    {
        Test((MyClass2)cl);
    }
    else
    {
        Test(cl);
    }
}

private void Test(MyClass cl)
{
    // Behaviour for MyClass
}

private void Test(MyClass2 cl2)
{
    // Behaviour for MyClass2
}

どちらの場合でも、MyClass2(またはMyClass) に対して直接コードを記述できます。リフレクションを実行したり、 を使用dynamicしたり、または... ジェネリック メソッドで実行する予定だったものは何でも - 分岐しtypeof(T)ますか?

于 2012-11-29T10:19:27.297 に答える
0

(コメントからの質問への回答)

これはかさばるソリューションであり、具体的な実装に依存していますが、次の行に沿って何かを行うことができます。

//initialization
Dictionary<Type, Action> typeActions = new Dictionary<Type, Action>();
typeActions.Add(typeof (MyClass), () => {Console.WriteLine("MyClass");});
typeActions.Add(typeof (MyClass2), () => {Console.WriteLine("MyClass2");});

private void TestGeneric<T>(T val)
{
   //here some error checking should be in place, 
   //to make sure that T is a valid entry class
   Action action = typeActions[val.GetType()];
   action();
}

このアプローチの欠点は、変数が正確に MyClass または MyClass2 型であることに依存することです。そのため、誰かが後で別のレベルの継承を追加すると、これは壊れますが、それでも、if-else や一般的な方法。

于 2012-11-29T09:10:22.830 に答える
0

ジェネリック メソッドを呼び出すと、実際の値の型ではなく、変数の型に基づいて型パラメーターが解決されます。

たとえば、次の場合:

var x = int as object;
Foo(x);

そして、あなたはこれを持っています:

void Foo<T>(T value)
{
}

次に、変数の型であるため、T の型はobjectand notになります。int

可能な解決策は、リフレクションまたはコンパイルされた式を使用して、値を最下位のサブクラスに動的にキャストすることです。

リフレクションを使用して、渡された値の実際の型をチェックし、それに基づいてロジックを作成するか、仮想メソッドなどの他の言語機構を使用することもできます。

解決しようとしているシナリオを説明すると、誰かが適切な解決策を提案する可能性があります。

于 2012-11-29T08:46:20.853 に答える
0

キャストとは、オブジェクトの型について、コンパイラが静的コードから推測できる以上のことを知っている場合です。

これにより、現在持っているより多くの型情報が必要になったときに、2 つのオプションが残されます。

  • 宣言時に情報を明示するように宣言を変更する
  • キャストする

または動的に行く

あなたの場合、最初の Foo もジェネリックに変更する必要があります

private void Foo<T>(T cl)
{
    //cl is actually MyClass2 instance
    TestGeneric(cl);
}

2番目のオプションはキャストを必要とし、複数のタイプがあると推測しているため、特に条件がオブジェクトのタイプに基づいている場合、一般的に悪い兆候である多くのif-else-ifが必要になります

private void Foo(MyClass cl)
{
    var mc2 = tcl as MyClass2;
    if(mc2 != null) {
        TestGeneric(mc2);
        return;
    }
    var mc3 = tcl as MyClass3;
    if(mc3 != null) {
        TestGeneric(mc3);
        return;
    }
    throw new InvalidOperationException("Type not recognised");
}

最後に、あなたはダイナミックに行くことができます

private void TestDynamic(dynamic val)
{
    TestGeneric(val);
}

ランタイム生成コードなど、動的に行う方法は他にもありますが、独自のロールを作成するよりも、単純に DLR を使用する方がはるかに簡単です。

于 2012-11-29T09:32:11.210 に答える