简体   繁体   English

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

[英]How to cast object to its actual type

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
}

After calling Foo() , the T in TestGeneric is MyClass , not MyClass2 . 调用Foo()之后 ,TestGeneric中的T是MyClass ,而不是MyClass2 How do I achieve treating val as a MyClass2 instance? 如何实现将val视为MyClass2实例? Thanks in advance. 提前致谢。

Upd: I don't actually know that the object has been created using MyClass2 ctor, but rather can infer this by calling val.GetType() so a simple as MyClass2 won't work Upd:我实际上并不知道该对象是使用MyClass2 ctor创建的,而是可以通过调用val.GetType()来推断出这一点,所以像MyClass2这样的简单对象将无法工作

It can be done with a visitor pattern . 可以用访客模式来完成。 It is a nice object oriented approach, when you have all handling code in a single handler class (not in each message) and if more message types will be needed, just add additional handler methods. 当您将所有处理代码都放在一个处理程序类中(而不是在每个消息中)并且如果需要更多消息类型时,只需添加其他处理程序方法,这是一种很好的面向对象的方法。

// 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);
    }
}

Assuming you can change Foo , but not its signature, you could do this: 假设您可以更改Foo ,但不能更改其签名,则可以执行以下操作:

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

This will resolve the version of TestGeneric that gets called at runtime instead of at compile time, calling TestGeneric<MyClass2> when cl is of that type. 这将解析在运行TestGeneric不是在编译时调用的TestGeneric<MyClass2>的版本,并在cl属于该类型时调用TestGeneric<MyClass2>

Well when you call a generic method, the type parameters will be resolved based on the types of the variables, and not based on the types of the actual values. 好吧,当您调用泛型方法时,类型参数将基于变量的类型而不是实际值的类型进行解析。

So for example if you have: 因此,例如,如果您有:

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

and then you have this: 然后你有这个:

void Foo<T>(T value)
{
}

Then the type of T will be object and not int , because that's the type of the variable. 然后T的类型将是object而不是int ,因为那是变量的类型。

A possible solution would be to dynamically cast the value to the lowest subclass, using either reflection or a compiled expression. 一种可能的解决方案是使用反射或已编译的表达式将值动态转换为最低的子类。

Some other alternatives you have are to use reflection to check the actual type of the value that was passed and base your logic on that, or use other language mechanics such as virtual methods. 您必须使用的其他一些替代方法是使用反射来检查传递的值的实际类型,并以此为基础进行逻辑计算,或者使用其他语言机制(例如虚拟方法)。

If you describe the scenario you are trying to solve, someone could probably suggest a suitable solution. 如果您描述要解决的方案,则可能有人会建议合适的解决方案。

Best solution would be to change Foo method to be generic too, so that you can save type information. 最好的解决方案是将Foo方法也更改为通用方法,以便您可以保存类型信息。 You should do this like so: 您应该这样做:

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

Otherwise, you would have an example of bad design. 否则,您将有一个不良设计的例子。 Simple way out would be 简单的出路是

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

You could also do a broader solution using reflection, but that would be abuse of tools to patch bad design. 您也可以使用反射来做一个更广泛的解决方案,但这将滥用工具来修补不良设计。

Following would be a reflection based solution, but I did not run it so bear with me and try to fix possible errors. 以下是基于反射的解决方案,但是我没有运行它,所以请耐心尝试修复可能的错误。

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);
}

So 所以

  1. Get type of class in which your TestGeneric method is defined 获取在其中定义TestGeneric方法的类的类型
  2. Use Type.GetMethod to retrieve method definition 使用Type.GetMethod检索方法定义
  3. Retrieve actual type of your cl variable to construct generic method 检索cl变量的实际类型以构造泛型方法
  4. Use MethodInfo.MakeGenericMethod to construct a method for specific type 使用MethodInfo.MakeGenericMethod构造特定类型的方法
  5. Use MethodBase.Invoke to invoke constructed method 使用MethodBase.Invoke调用构造方法

Your code will differ based on your current implementation (depending on type names, method accessibility and such). 您的代码将根据您当前的实现而有所不同(取决于类型名称,方法可访问性等)。

(Answering the question from the comment) (从评论中回答问题)

It's a bulky solution, and you are depending on concrete implementations, but you could do something along these lines: 这是一个庞大的解决方案,您依赖于具体的实现,但是您可以按照以下方式做一些事情:

//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();
}

A downside to this approach is that it depends on the variables being exactly of type MyClass or MyClass2, so if someone later adds another level of inheritance, this will break, but still, it's more flexible than a if-else or a switch in the generic method. 这种方法的缺点是,它取决于变量的类型完全是MyClass或MyClass2,因此,如果以后有人添加另一个继承级别,这将中断,但仍然比if-else或int中的切换更灵活。通用方法。

A cast is when you know more about the type of an object than the compiler is able to deduce from the static code. 强制类型转换是指您比编译器从静态代码推断出的更多有关对象类型的知识。

This leaves you with two options when ever you need more type information that you currently have. 当您需要当前拥有的更多类型信息时,这将为您提供两个选择。

  • Change the declaration to make the information explicit at declaration 更改声明以使声明时的信息明确
  • Make a cast 进行演员表

or go dynamic 或动态

In your case the first would require changing Foo to be generic too 在您的情况下,第一个要求将Foo也更改为通用名称

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

The second option would require casting and I'm guessing you have multiple types so you'll need a lot of if-else-if's which is generally a bad sign especially when the condition is based on the type of an object 第二个选项需要强制转换,我猜您有多种类型,因此您将需要大量的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");
}

lastly you could go dynamic 最后你可以动起来

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

There are other ways of doing it dynamically such as runtime generating code but it's a lot easier to simply use the DLR than trying to role your own on 还可以通过其他方式动态地执行此操作,例如运行时生成代码,但是简单地使用DLR比尝试自己扮演角色要容易得多

You don't want to call a generic method here. 您不想在这里调用泛型方法。 Once you enter TestGeneric<T> , even if T is MyClass2 as you want, you can't have written any code against MyClass2 (or even MyClass , unless you add a restriction on T ) so it doesn't help! 一旦输入TestGeneric<T> ,就算您想要的是TMyClass2 ,也无法针对MyClass2 (甚至MyClass ,除非您对T添加了限制)编写了任何代码,因此它无济于事!

You certainly don't need to go down the route of reflection or dynamic . 您当然不必走反思或dynamic

Most obvious way to do this: put the class-specific behaviour in the class itself: 最明显的方法是:将特定于类的行为放在类本身中:

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: branch code depending on the type passed: 次佳:分支代码取决于所传递的类型:

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
}

In both these cases you can write code directly against MyClass2 (or MyClass ) without having to do any reflection, use dynamic , or... whatever you were planning to do in your generic method - branch on typeof(T) ? 在这两种情况下,您都可以直接针对MyClass2 (或MyClass )编写代码,而无需进行任何反射,使用dynamic或...您打算在常规方法中执行的任何操作MyClass2 typeof(T)上的分支?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM