Richard Deeming
Проблема в том, что универсальные типы по умолчанию инвариантны:
Ковариация и контравариантность в дженериках[^]
C# 4.0: Ковариация И Контравариантность В Дженериках[^]
Вы можете упростить пример:
public class A { }
public class B : A { }
public class Wrapper<T> where T : A { }
Wrapper<B> bWrapper = new Wrapper<B>();
Wrapper<A> aWrapper = bWrapper; // <-- Compiler error
Универсальными классами являются
всегда инвариантный. Однако универсальные интерфейсы и делегаты могут быть инвариантными, ковариантными или контравариантными для каждого параметра типа.
(Различные параметры типа могут иметь различное поведение, хотя это иногда может привести к путанице.)Инвариантный это означает, что вы не можете рассматривать закрытый универсальный тип с одним параметром типа как один и тот же универсальный тип с другим параметром типа, независимо от каких-либо отношений наследования / реализации между двумя типами параметров.
IWrapper<A>
и
IWrapper<B>
это совершенно разные типы, между которыми нет никакой связи.
Ковариантный означает, что вы можете лечить экземпляр
IWrapper<B>
в качестве примера
IWrapper<A>
, до тех пор, пока
B
наследует / реализует
A
.
Чтобы что-то было
ковариантный, параметр type может использоваться только в возвращаемом типе метода или типе свойства только для чтения.
public interface IWrapper<out T>
{
T MyValue { get; }
IWrapper<T> NextValue();
}
IWrapper<B> bWrapper = ...;
IWrapper<A> aWrapper = bWrapper;
Контравариантный означает, что вы можете лечить экземпляр
IWrapper<A>
в качестве примера
IWrapper<B>
, до тех пор, пока
B
наследует / реализует
A
.
(Это называется "contra", потому что отношение между закрытыми универсальными типами течет в противоположном направлении к отношению между параметрами типа.)
Чтобы что-то было
контравариантный, параметр type может использоваться только в типе входного параметра
(не украшен ref
или out
), или тип свойства только для записи.
public interface IWrapper<int T>
{
T Minimum { set; }
T Maximum { set; }
bool Contains(T value);
}
IWrapper<A> aWrapper = ...;
IWrapper<B> bWrapper = aWrapper;
Если параметр типа относится к обеим категориям, универсальный тип должен быть инвариантным для этого параметра.
В вашем примере вам нужен контроллер для
A
быть ковариантным. Предполагая, что вы используете параметр type только разрешенными способами, вам просто нужно извлечь интерфейс:
public interface IAController<out T> where T : A
{
}
public abstract class AController<T> : IAController<T> where T : A
{
}
public abstract class BController<T> : AController<T> where T : B
{
}
public abstract class A
{
IAController<A> controller;
public A(IAController<A> controller)
{
this.controller = controller;
}
}
public abstract class B : A
{
public B(BController<B> controller) : base(controller)
{
}
}
NB: Если вы используете параметр type для
AController
в позиции ввода вы получите ошибку компилятора.