Невозможно наложить производный тип на базовый абстрактный класс с параметром типа

У меня есть простой метод factory, который предоставляет конкретный экземпляр реализации на основе предоставленного общего параметра типа. Если конкретные классы наследуют от общего абстрактного базового класса с параметром типа, я не могу их отличить. Компилятор сообщает мне Error 2 Cannot convert type 'Car' to 'VehicleBase<T>'. Он отлично работает, если я подменяю абстрактный класс для интерфейса с одним и тем же параметром типа, или если я удалю параметр типового типа из абстрактного класса.

interface IWheel
{
}

class CarWheel : IWheel
{
}

abstract class VehicleBase<T>
{
}

class Car : VehicleBase<CarWheel>
{
}

class VehicleFactory
{
    public static VehicleBase<T> GetNew<T>()
    {
        if (typeof(T) == typeof(CarWheel))
        {
            return (VehicleBase<T>)new Car();
        }
        else
        {
            throw new NotSupportedException();
        }
    }
}

Это не удается скомпилировать на (VehicleBase<T>)new Car(). Является ли это дефектом компилятора, или это может быть преднамеренное дизайнерское решение для обработки абстрактных классов и интерфейсов с параметрами типа по-разному?

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

Ответ 1

Это не является дефектом компилятора или преднамеренным решением. Параметры типа для общих классов не являются ковариантными или контравариантными, то есть нет отношения наследования между специализациями одного и того же родового класса. Из документов:

В .NET Framework версии 4 параметры типа варианта ограничены общим интерфейсом и универсальными типами делегатов.

Это означает, что следующий код будет скомпилирован, поскольку вместо абстрактного класса он использует интерфейс:

interface IWheel
{
}

class CarWheel : IWheel
{
}

interface IVehicleBase<T> 
{
}

class Car : IVehicleBase<CarWheel>
{
}

class VehicleFactory
{
    public static IVehicleBase<T> GetNew<T>() 
    {
        if (typeof(T) == typeof(CarWheel))
        {
            return (IVehicleBase<T>)new Car();
        }
        else
        {
            throw new NotSupportedException();
        }
    }
}

Проверьте "" Ковариация и контравариантность в универсалах" для получения дополнительной информации и примеров.

Существует также Часто задаваемые вопросы по ковариации и контравариантности на блоге с часто задаваемыми вопросами С# с дополнительной информацией и 11-рядная серия! по теме Эрик Липперт

Ответ 2

Это не доказуемо, потому что общий код должен работать (с тем же IL) для каждого возможного T, и нет ничего, что можно сказать, что Car : VehicleBase<float>, например. Компилятор не переоценивает тот факт, что проверка if показывает, что T - CarWheel - статический контролер обрабатывает каждое утверждение отдельно, он не пытается понять причину и следствие условий.

Чтобы заставить его, нажмите на object посередине:

return (VehicleBase<T>)(object)new Car();

Однако! Ваш подход не является действительно "общим" как таковым.

Ответ 3

Это работает:

return new Car() as VehicleBase<T>;

Мое предположение, почему это так:
Поскольку экземпляры типичного типа VehicleBase<T> не связаны друг с другом, не может быть доказано, что их использование может работать:

enter image description here

Если T имеет тип Blah, приведение не будет работать. Вы не можете вернуться к объекту, а затем взять другую ветку (в конце концов, нет множественного наследования в С#).

Отбросив его до object раньше, вы снова открываете возможность того, что бросок может работать, потому что все еще может быть путь до VehicleBase<CarWheel>. Интерфейс, конечно, может появляться в любом месте этого дерева ниже object, так что это тоже должно работать.