如何将对象转换为其实际类型

7

Consider the following piece of code:

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 构造函数创建的,但可以通过调用 val.GetType() 推断出来,因此简单的 MyClass2 并不适用。


1
你为什么要包含 as 强制转换呢? - Shmiddty
你能给我们更多细节,关于你想通过这段代码得到什么吗? - Kirill Bestemyanov
这是解释: 我收到一个对象,它被传递为 MyClass 的实例,而实际上它是 MyClass2 的实例。private void HandleSomeMessage(MyClass cl)我想能够调用一个泛型方法void HandleMessageGeneric(T cl)并使用实际的 cl 类型。 - Gena Verdel
为什么要调用一个通用方法?这并不会使编写特定于传递的“MyClass”类型的代码更容易。 - Rawling
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);
    }
}

据我所知,上述方法是解决 IMessageHandler 接口共享实例的最佳方案。然而,情况并非如此,因为每个注入该依赖项的类都必须解析一个新实例来处理任意数量的消息。这就是将当前实现转换为通用实现的意图。 - Gena Verdel

1

假设您可以更改Foo,但不能更改其签名,您可以这样做:

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

这将解决在运行时而不是编译时调用TestGeneric版本的问题,当clMyClass2类型时,调用TestGeneric<MyClass2>


DLR使用的大致惩罚是什么? - Gena Verdel
@GenaVerdel 对于性能问题,像往常一样,请先尝试并确定是否满足您的需求。 - Matthew Strawbridge

0

当您调用通用方法时,类型参数将根据变量的类型进行解析,而不是根据实际值的类型。

例如,如果您有:

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

然后你得到这个:

void Foo<T>(T value)
{
}

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

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

您可以使用反射来检查传递的值的实际类型并基于此构建逻辑,或者使用其他语言机制,如虚拟方法。

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


那么T的类型将是object而不是int吗?你确定吗?应该是int而不是object。 - Vano Maisuradze
1
value.GetType() 返回 int。typeof(T) 返回 object。 - SWeko

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 调用构造的方法

你的代码将根据当前实现而有所不同(取决于类型名称、方法可访问性等)。


Foo()方法的签名不允许更改。 这是一个糟糕的设计,但很遗憾,我不能修改它... 因此,我需要想出一个“适配器”。 您提出的其他建议是我试图避免的。 还有其他建议吗? - Gena Verdel
另一种解决方案是使用反射。虽然比第二种解决方案更通用,但它更加丑陋。我认为除了这三种方法之外没有其他有意义的方式。 - Nikola Radosavljević

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更灵活。

最终目标是能够将 MyClass2 的实例传递给泛型方法,而不是在方法内部解析其类型... - Gena Verdel
通用调用解析是基于变量类型而不是变量引用的实例类型完成的,所以我认为你可能会很遗憾。 - SWeko
@GenaVerdel,你需要采用动态方法来解决这个问题。请参考我的回答中的第三个选项(https://dev59.com/MWvXa4cB1Zd3GeqPJXUc#13622650)。 - Rune FS

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 比尝试自己开发更容易。


0

在这里,您不想调用通用方法。一旦进入TestGeneric<T>,即使T是您想要的MyClass2,您也不能针对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();
}

下一步:根据传入的类型返回分支代码。
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)分支?

你所描述的方法是目前的方法。此外,我无法修改 MyClass 或 MyClass2 的代码。 - Gena Verdel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接