Как привести объект к его фактическому типу


Рассмотрим следующий фрагмент кода:

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(), T в TestGeneric является MyClass, а не MyClass2. Как добиться того, чтобы val рассматривался как MyClass2 экземпляр? Заранее спасибо.

Upd: Я на самом деле не знаю, что объект был создан с помощью MyClass2 ctor, но скорее могу сделать вывод, вызвав val.GetType () так просто, как MyClass2 не будет работать

7 7

7 ответов:

Предполагая, что вы можете изменить Foo, но не его подпись, вы можете сделать следующее:

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

Это разрешит версию TestGeneric, которая вызывается во время выполнения, а не во время компиляции, вызывая TestGeneric<MyClass2>, Когда cl имеет этот тип.

Это можно сделать с помощью шаблона посетителя. Это хороший объектно-ориентированный подход, когда у вас есть весь код обработки в одном классе обработчика (не в каждом сообщении), и если потребуется больше типов сообщений, просто добавьте дополнительные методы обработчика.

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

Ну, когда вы вызываете универсальный метод, параметры типа будут разрешаться на основе типов переменных, а не на основе типов фактических значений.

Так, например, если у вас есть:

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

И тогда у вас есть это:

void Foo<T>(T value)
{
}

Тогда тип T будет object, а не int, потому что это тип переменной.

Возможным решением было бы динамически привести значение к низшему подклассу, используя либо отражение, либо скомпилированный выражение.

Некоторые другие альтернативы, которые у вас есть, это использовать отражение, чтобы проверить фактический тип передаваемого значения и основать свою логику на этом, или использовать другие языковые механики, такие как виртуальные методы.

Если вы опишете сценарий, который пытаетесь решить, кто-то, вероятно, предложит подходящее решение.

(отвечая на вопрос из комментария)

Это громоздкое решение, и вы зависите от конкретных реализаций, но вы могли бы сделать что-то в этом роде:

//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 или переключатель в универсальном методе.

Приведение-это когда вы знаете о типе объекта больше, чем компилятор может вывести из статического кода.

Это оставляет вам два варианта, когда вам понадобится больше информации о типе, которая у вас есть в данный момент.

  • измените объявление, чтобы сделать информацию явной при объявлении
  • сделать слепок

Или go dynamic

В вашем случае первое потребует изменения 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, чем пытаться играть свою собственную роль на

Лучшим решением было бы изменить метод 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. Используйте Тип .GetMethod для получения определения метода
  3. получить фактический тип переменной cl для построения универсального метода
  4. Используйте MethodInfo.MakeGenericMethod для построения метода для конкретного типа
  5. Используйте MethodBase.Invoke для вызова сконструированного метода

Ваш код будет отличаться в зависимости от текущего реализация (в зависимости от имен типов, доступности метода и так далее).

Здесь не нужно вызывать универсальный метод. Как только вы введете 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)?