Подтвердить что ты не робот

Общий метод для литья

Я пытаюсь написать общий метод для создания типов. Я хочу написать что-то вроде Cast.To<Type>(variable) вместо (Type) variable. Моя неправильная версия этого метода:

public class Cast
{
    public static T To<T>(object o)
    {
        return (T) o;
    }
}

И это простой тест:

public class A
{
    public static explicit operator B(A a)
    {
        return new B();
    }
}

public class B
{
}

A a = new A();
B b = Cast.To<B>(a);

Как вы уже догадались, этот код не сработает с InvalidCastException.

Неужели этот код терпит неудачу, потому что виртуальная машина не знает, как передать переменную типа object для ввода B во время выполнения? Но сообщение об исключении говорит: "невозможно передать объект типа A в тип B". Итак, CLR знает о реальном типе переменной o, почему он не может выполнить кастинг?

И вот главный вопрос: как мне переписать метод T To<T>(object o), чтобы исправить эту проблему?

4b9b3361

Ответ 1

Если вы можете использовать С# 4.0, это работает:

namespace CastTest
{
    internal class Program
    {
        private static void Main(string[] args)
        {

            A a = new A();
            B b = Cast.To<B>(a);
            b.Test();

            Console.Write("Done.");
            Console.ReadKey();
        }

        public class Cast
        {
            public static T To<T>(dynamic o)
            {
                return (T)o;
            }
        }

        public class A
        {
            public static explicit operator B(A a)
            {
                return new B();
            }
        }

        public class B
        {
            public void Test()
            {
                Console.WriteLine("It worked!");
            }
        }

    }
}

Ответ 2

Все сказанное о разрешении оператора правильное... но это мой ответ на ваш главный вопрос:

    public static T To<T>(this object o)
    {
        return (T)(dynamic)o;
    }

Ключевым моментом здесь является то, что приведение o к динамике заставит .NET искать явный оператор во время выполнения.

Кроме того, почему бы не сделать его методом расширения?

Вместо

        A a = new A();
        B b = Cast.To<B>(a);

вы можете сделать

        A a = new A();
        B b = a.To<B>();

Дополнительным преимуществом раскрытия его в качестве метода расширения является то, что вы получаете свободный интерфейс для явного литья (если вам нравится такая штука). Я всегда ненавидел количество вложенных скобок, необходимых для явного кастинга в .NET.

Итак, вы можете сделать:

a.To<B>().DoSomething().To<C>().DoSomethingElse() 

вместо

((C)((B)a).DoSomething())).DoSomethingElse()

который для меня выглядит более ясным.

Ответ 3

Вы можете сделать этот трюк, найдя правильные методы через Reflection:

public static T To<T> (object obj)
{
    Type sourceType = obj.GetType ();
    MethodInfo op = sourceType.GetMethods ()
                    .Where (m => m.ReturnType == typeof (T))
                    .Where (m => m.Name == "op_Implicit" || m.Name == "op_Explicit")
                    .FirstOrDefault();

    return (op != null)
        ? (T) op.Invoke (null, new [] { obj })
        : (T) Convert.ChangeType (obj, typeof (T));
}

В .NET 4.0 вы можете использовать ключевое слово dynamic, как предложено в других ответах.

Ответ 4

Ваш Cast.To<T>() просто пытается интерпретировать ссылку на данный объект как ссылку на T. Что не удается, конечно.

И если компилятор встречается с (B) a и знает, что a имеет тип a, а тип a имеет оператор выполнения времени компиляции для ввода B - он испускает это приведение. Это не ваше дело.

Ответ 5

Экземпляр a является объектом до момента каста до B. Не тип a, но object. Таким образом, невозможно отбрасывать object в B из-за того, что CLR не может знать, что o содержит явный оператор.
EDIT:
Да! Вот решение:

public class Cast
{
    public static T1 To<T1>(dynamic o)
    {
        return (T1) o;
    }
}

Теперь CLR точно знает, что o является экземпляром типа a и может вызывать явный оператор.

Ответ 6

Вы никогда не сможете заставить это работать без "преобразователя типов" (ручной процесс сопоставления атрибутов для всех известных типов, которых просто не произойдет). Вы просто не можете просто передать один несвязанный конкретный класс другому. Это сломало бы единую модель наследования (которая является одним из определяющих принципов современного ООП - читать "Проблему алмаза" )

Было также отмечено, что интерфейсы (полиморфизм) - оба класса должны были бы также выводиться из одного и того же интерфейса (который находится в одной строке)

Ответ 7

Я столкнулся с этой проблемой несколько раз, и я не чувствую, что OOP "грязный", когда я могу ограничиться типами, реализующими интерфейс IConvertible. Тогда решение действительно становится очень чистым!

private T To<T>(object o) where T : IConvertible
{
    return (T)Convert.ChangeType(o, typeof(T));
}

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

Поскольку он использует класс Convert, компилятор действительно будет иметь информацию, чтобы знать, что делать. Это не просто прост.

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

Я считаю крайне важным, чтобы кастинг действительно срабатывал, когда кто-то специально написал метод для задания, чтобы избежать такой ситуации, как Add(x, y) только для определенных значений x и y.

Я думаю, что ожидание отличается, если вы попробуете кастинг самостоятельно, как в T1 x = (T1) T2 y. Затем я думаю, что более очевидно, что вы по-настоящему сами по себе, так как вы просто создали какой-то актерский состав, а не называли "обложка всех методов приведения".

В этом случае ясно, что он конкретно касается объектов, реализующих IConvertible, и разработчик может предположить, что он будет хорошо работать с любым из этих объектов.

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

Ответ 8

Возможно, не то, что вам нужно делать точно, но это будет работать:

public class Cast
{
    public static targetType To<soureType, targetType>(object o)
    {
        return (targetType)((sourceType) o);
    }
}

Но хорошо, такой метод мне кажется бесполезным...