С# литье унаследованного универсального интерфейса
У меня возникают проблемы с созданием интерфейса, который я придумал.
Это MVP-дизайн для С# Windows Forms. У меня есть класс IView, который я реализую на своих классах форм. Там также IPresenter, который я получаю в различных конкретных докладчиков. Каждый Presenter будет управлять IView по-разному в зависимости от роли, например, открывая диалог для ввода нового набора данных с помощью AddPresenter, а не для редактирования существующих данных с помощью EditPresenter, который предварительно загружал данные в форму. Каждый из них наследуется от IPresenter. Я хочу использовать код как таковой:
AddPresenter<ConcreteView> pres = new AddPresenter<ConcreteView>();
В основном я работаю, но эти ведущие и те взгляды, которыми они управляют, объединены в плагины, которые загружаются после выполнения, что означает, что мне нужен класс Manager, который действует как интерфейс плагина, принимает параметр "mode". Этот параметр режима используется для метода factory для создания либо добавления, либо редактирования презентатора, а потому, что вызов для отображения диалога выполняется позже, тогда мне нужно сделать вызов через интерфейс IPresenter следующим образом:
private IPresenter<IView> pres;
public ShowTheForm()
{
pres.ShowDialog();
}
Теперь у меня возникают проблемы, когда дело доходит до того, что мой конкретный экземпляр AddPresenter скажет члену "pres".
Здесь сокращена упрощенная версия того, что у меня есть:
interface IView
{
void ViewBlah();
}
interface IPresenter<V> where V : IView
{
void PresBlah();
}
class CView : IView
{
public void ViewBlah()
{
}
}
class CPresenter<T> : IPresenter<T> where T : IView
{
public void PresBlah()
{
}
}
private void button3_Click(object sender, EventArgs e)
{
CPresenter<CView> cpres = new CPresenter<CView>();
IPresenter<IView> ipres = (IPresenter<IView>)cpres;
}
Это ошибка:
Unable to cast object of type 'CPresenter`1[MvpApp1.MainForm+CView]' to type 'IPresenter`1[MvpApp1.MainForm+IView]'.
Как спецификатор Presenter, так и тип Generic из того, что я могу сказать подклассам ARE интерфейсов, поэтому я не могу понять, почему он не будет выполняться.
Любые мысли?
Стив
Ответы
Ответ 1
Проблема - это параметр общего типа. Если вы сделаете параметр интерфейса ковариантным, то приведение будет работать.
Это достигается добавлением ключевого слова out
, например:
interface IPresenter<out V> where V : IView
{
void PresBlah();
}
Вы можете узнать больше о том, как это работает со следующей статьей MSDN: Ковариация и контравариантность в универсальных файлах. Раздел Общие интерфейсы с параметрами ковариационного типа специально относится к вашему вопросу.
Обновление: убедитесь, что вы проверяете комментарии между @phoog и мной. Если ваш фактический код принимает V
как вход, вы не сможете сделать его ковариантным. Связанная статья и ответ @phoog объясняют это дело более подробно.
Ответ 2
CPresenter<CView>
не является IPresenter<IView>
, так же как List<int[]>
не является IList<IEnumerable>
.
Подумайте об этом. Если вы можете получить ссылку IList<IEnumerable>
на List<int>
, вы можете добавить к ней string[]
, которая должна была бы вызвать исключение. Весь смысл проверки статического типа заключается в предотвращении компиляции такого кода.
Если интерфейс позволяет, вы можете объявить параметр типа как ковариантный (IPresenter<out V> where V : ...
. Тогда интерфейс будет вести себя как IEnumerable<out T>
. Это возможно только в том случае, если параметр типа никогда не используется во входной позиции.
Чтобы вернуться к примеру List<int[]>
, безопасно рассматривать его как IEnumerable<IEnumerable>
, потому что вы не можете добавить что-либо в ссылку IEnumerable<T>
; вы можете читать только что-то из этого, и, в свою очередь, безопасно обрабатывать int[]
как IEnumerable
, так что все хорошо.