Невозможно наложить производный тип на базовый абстрактный класс с параметром типа
У меня есть простой метод 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
, так что это тоже должно работать.