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 是MyClass,而不是MyClass2。如何实现将val视为MyClass2实例?提前致谢。

更新:我实际上并不知道该对象是使用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>whencl是该类型的。

于 2012-11-29T09:49:51.470 回答
0

那么当你调用一个泛型方法时,类型参数将根据变量的类型来解析,而不是基于实际值的类型。

例如,如果您有:

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

然后你有这个:

void Foo<T>(T value)
{
}

那么 T 的类型将是object而不是int,因为那是变量的类型。

一种可能的解决方案是使用反射或编译表达式将值动态转换为最低的子类。

您还有其他一些选择是使用反射来检查传递的值的实际类型并将您的逻辑基于该类型,或者使用其他语言机制,例如虚拟方法。

如果您描述您要解决的方案,有人可能会建议一个合适的解决方案。

于 2012-11-29T08:46:20.853 回答
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

(从评论中回答问题)

这是一个庞大的解决方案,您依赖于具体的实现,但您可以按照以下方式做一些事情:

//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 或 switch 更灵活通用方法。

于 2012-11-29T09:10:22.830 回答
0

强制转换是指您对对象类型的了解比编译器从静态代码中推断出的更多。

当您需要更多当前拥有的类型信息时,这为您提供了两种选择。

  • 更改声明以使信息在声明时显式
  • 制作演员表

或动态

在您的情况下,第一个也需要将 Foo 更改为通用的

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

第二个选项需要强制转换,我猜你有多种类型,所以你需要很多 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 回答
0

您不想在这里调用通用方法。一旦你进入TestGeneric<T>,即使TMyClass2你想要的,你也不能写任何代码反对MyClass2(甚至MyClass,除非你在 上添加限制T)所以它没有帮助!

你当然不需要走反思路线或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();
}

Next-best:分支代码取决于传递的类型:

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 回答