Общий метод преобразования throw InvalidCastException

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

Так что сценарий довольно прост. У меня есть тот же сервис, который возвращает мне список элементов типа External. У меня есть свой собственный класс WrapperExternal, который просто оборачивает этот класс и предоставляет ему некоторые дополнительные функции. У меня есть некоторый другой набор классов, которые наследуются от WrapExternal и добавляют различные функциональные возможности.

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

Код моего приложения:

    static void Main(string[] args)
    {
        var items = GetItemsFromServer();
        var converted = ConvertItems<SubWrapperExternal>(items).ToList();
    }

    public static IEnumerable<T> ConvertItems<T>(IEnumerable<External> externalItems) where T : WrapperExternal
    {
        return externalItems
            .Where( item => true)
            .Select(item => (T)item);
    }

Когда вы попытаетесь запустить этот код, вы получите исключение в строке (T):

An unhandled exception of type 'System.InvalidCastException' occurred in ConsoleApplication1.exe 
Additional information: 
Unable to cast object of type 'ConsoleApplication1.WrapperExternal' to type 'ConsoleApplication1.SubWrapperExternal'.

Вы знаете, как я могу сделать это, чтобы работать?

Код тестового приложения:

namespace ConsoleApplication1
{
class Program
{
    static void Main(string[] args)
    {
        var items = GetItemsFromServer();

        var converted = ConvertItems<SubWrapperExternal>(items).ToList();

    }

    private static List<External> GetItemsFromServer()
    {
        return new List<External>
        {
            new External{Name = "A"},
            new External{Name = "B"},
            new External{Name = "C"},
        };
    }

    public static IEnumerable<T> ConvertItems<T>(IEnumerable<External> externalItems) where T : WrapperExternal
    {
        return externalItems
            .Where( item => true)
            .Select(item => (T)item);
    }
}

class External
{
    public string Name { get; set; }

}

class WrapperExternal
{
    public External External { get; private set; }

    public WrapperExternal(External external)
    {
        External = external;
    }

    public static explicit operator WrapperExternal(External item)
    {
        return item != null ? new WrapperExternal(item) : null;
    }

    public static implicit operator External(WrapperExternal item)
    {
        return item != null ? item.External : null;
    }
}

class SubWrapperExternal : WrapperExternal
{
    public SubWrapperExternal(External external)
        : base(external)
    {
    }

    public static explicit operator SubWrapperExternal(External item)
    {
        return item != null ? new SubWrapperExternal(item) : null;
    }

    public static implicit operator External(SubWrapperExternal item)
    {
        return item != null ? item.External : null;
    }
}
}

1 ответ

Решение

Операторы преобразования являются ошибкой для использования с обобщениями - обобщенные элементы не поддерживают никаких статических перегрузок операторов. Из-за этого (T) cast выполняет проверку неконвертирующего типа (генерики должны использовать один и тот же IL для каждого возможного Tзапомни) - базовый castclass,

Единственный "простой" способ делать то, что вы хотите, это обманывать с dynamic:

return externalItems.Select(item => (T)(dynamic)item);

Поскольку C#-специфичен dynamic провайдер знает все общие правила C#, знает операторы преобразования и будет применять их по требованию. С этим связано небольшое снижение производительности, но оно не так плохо, как кажется на первый взгляд, поскольку стратегия кэшируется (как IL) один раз для каждого типа - она ​​не выполняет отражение для каждого элемента.

Другие вопросы по тегам