【问题标题】:How to cast object to its actual type如何将对象转换为其实际类型
【发布时间】:2019-09-07 04:29:37
【问题描述】:

考虑以下代码:

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 不会工作

【问题讨论】:

  • 你为什么要加入 as 演员表?
  • 您能否详细介绍一下您希望使用此代码获得什么?
  • 解释如下:我收到一个作为 MyClass 实例传输的对象,而它实际上是 MyClass2 的一个实例。 private void HandleSomeMessage(MyClass cl) 我希望能够使用 ACTUAL cl 的类型调用通用方法 void HandleMessageGeneric(T cl)
  • 为什么要调用泛型方法?它不会让编写特定于被传递的MyClass 的实际类型的代码变得更容易。

标签: c# .net upcasting


【解决方案1】:

可以通过visitor pattern 完成。这是一种很好的面向对象方法,当您在单个处理程序类中拥有所有处理代码(而不是在每个消息中)并且如果需要更多消息类型,只需添加额外的处理程序方法。

// 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 接口的共享实例的情况。但是,情况并非如此,因为每个注入了此依赖项的类都必须解析一个新实例,其中包含要处理的任意数量的消息。这就是将当前实现转换为通用实现的意图。
【解决方案2】:

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

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

这将解析在运行时而不是在编译时调用的TestGeneric 的版本,当cl 属于该类型时调用TestGeneric&lt;MyClass2&gt;

【讨论】:

  • 使用 DLR 的近似惩罚是多少?
  • @GenaVerdel 与性能一样,尝试一下,然后看看它是否足够快以满足您的需求。
【解决方案3】:

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

例如,如果您有:

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

然后你有这个:

void Foo<T>(T value)
{
}

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

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

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

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

【讨论】:

  • "那么 T 的类型将是 object 而不是 int" 你确定吗?它不是对象。
  • value.GetType() 返回inttypeof (T)返回object
【解决方案4】:

(从评论中回答问题)

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

//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 的实例传递给泛型方法,而不是在方法内部解析其类型...
  • 通用调用解析是在变量的类型上完成的,而不是在变量引用的实例的类型上完成的,所以我认为你可能不走运。
  • @GenaVerdel 你需要动态解决这个问题。看我回答的第三个选项(stackoverflow.com/a/13622650/112407
【解决方案5】:

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

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

  • 更改声明以在声明中明确说明信息
  • 制作演员表

或动态

在您的情况下,第一个需要将 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 比尝试自己扮演角色要容易得多

【讨论】:

    【解决方案6】:

    您不想在这里调用泛型方法。一旦你输入TestGeneric&lt;T&gt;,即使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)?

    【讨论】:

    • 您描述的方法是当前的方法。此外,我不能修改 MyClass 和 MyClass2 代码
    【解决方案7】:

    最好的解决方案是将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() 方法的签名不能更改。这是一个糟糕的设计,但遗憾的是,我不允许修改它......因此我需要想出一个“适配器”。你的另一个建议是我试图避免的。还有其他建议吗?
    • 其他解决方案是使用反射。它比第二种解决方案更丑陋但更通用。除了这 3 个,我没有看到其他有意义的方法。
    猜你喜欢
    • 2012-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-20
    • 1970-01-01
    • 2014-10-13
    • 2020-04-06
    相关资源
    最近更新 更多