Это проблема ковариации?Не уверен, что кирпичная стена
-
18-09-2019 - |
Вопрос
Я написал страницы ASP.NET, которые будут управлять формами.Они основаны на следующем базовом классе.
public abstract class FormPageBase<TInterface, TModel> : Page, IKeywordProvider
where TModel:ActiveRecordBase<MasterForm>, TInterface, new()
where TInterface:IMasterForm
{
public TInterface FormData { get; set; }
}
И пример подкласса здесь:
public partial class PersonalDataFormPage : FormPageBase<IPersonalDataForm, PersonalDataForm>, IHasFormData<IPersonalDataForm>, IHasContact
{
}
Ниже у меня есть пользовательский элемент управления на странице, который я хочу «потребить» «FormData» со страницы, чтобы он мог читать/записывать в него.
Затем у меня есть более «общий» пользовательский элемент управления, который я хочу использовать в базовом интерфейсе всех моих подклассов формы...IMasterForm
Но когда пользовательский элемент управления пытается привести Page.FormData (попытавшись привести страницу к IHasFormData<IMasterForm>
он говорит мне, что страница IHasFormData<IFormSubclass>
хотя у меня есть ограничение на IFormSubclass, которое говорит, что это также IMasterForm
Могу ли я в любом случае преобразовать общий подкласс в общий суперкласс или это «ковариация» и вещь C # 4.0?
public abstract class FormControlBase<T> : UserControl, IKeywordProvider
where T:IMasterForm
{
protected T FormData { get; set; }
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
//This cast is failing when my common control's T does not exactly match
// the T of the Page.. even though the common controls TInterface is a base interface to the
//pages TInterface
FormData = ((IHasFormData<T>) Page).FormData;
if (!IsPostBack)
{
PopulateBaseListData();
BindDataToControls();
}
}
protected abstract void PopulateBaseListData();
protected abstract void BindDataToControls();
public abstract void SaveControlsToData();
#region IKeywordProvider
public List<IKeyword> GetKeywords(string categoryName)
{
if(!(Page is IKeywordProvider ))
throw new InvalidOperationException("Page is not IKeywordProvider");
return ((IKeywordProvider) Page).GetKeywords(categoryName);
}
#endregion
}
Решение
Позвольте мне сначала посмотреть, смогу ли я еще раз сформулировать эту сложную проблему более кратко.У вас есть общий интерфейс IHasFormData<T>
.У вас есть объект, который, как известно, реализует IHasFormData<IFormSubclass>
.Вы хотите преобразовать его в IHasFormData<IMasterForm>
.Вы знаете, что существует преобразование ссылок из IFormSubclass в IMasterForm.Это терпит неудачу.
Да?
Если это правильная постановка задачи, то да, это вопрос ковариантности интерфейса.C# 3 не поддерживает ковариацию интерфейса.С# 4 будет, если вы сможете доказать компилятору, что ковариация безопасна.
Позвольте мне кратко описать вам, почему это может быть небезопасно.Предположим, у вас есть классы Apple, Orange и Fruit с очевидными отношениями подклассов.У вас есть IList<Apple>
к которому вы хотите применить IList<Fruit>
.Такое ковариантное преобразование недопустимо в C# 4 и не может быть допустимым, поскольку оно небезопасно.Предположим, мы это разрешили.Затем вы могли бы сделать это:
IList<Apple> apples = new List<Apple>();
IList<Fruit> fruits = apples;
fruits.Add(new Orange());
// We just put an orange into a list of apples!
// And now the runtime crashes.
Обратите внимание, что проблема в том, что List<T>
предоставляет метод, который принимает T в качестве аргумента.Чтобы компилятор разрешил ковариантные преобразования в вашем интерфейсе IHasFormData<T>
, вы должны доказать компилятору, что IHasFormData<T>
не предоставляет ничего, что принимает T в качестве аргумента.Вы сделаете это, объявив интерфейс IHasFormData<out T>
, мнемоническое значение «T появляется только в выходных позициях».Затем компилятор проверит правильность вашего утверждения и начнет разрешать ковариантные преобразования.
Дополнительную информацию об этой функции в C# 4 см. в моем архиве заметок о дизайне этой функции:
http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx
Другие советы
C# до версии 4.0 требует, чтобы все приведения к универсальным типам точно соответствовали параметру типа.В версии 4.0 представлены ко- и контрвариантность, но приведение, которое вы пытаетесь выполнить, невозможно в более ранних версиях.
У меня очень похожая базовая страница на вашу, вот как я определяю свою.
public abstract class ViewBasePage<TPresenter, TView> : Page, IView
where TPresenter : Presenter<TView>
where TView : IView
{
protected TPresenter _presenter;
public TPresenter Presenter
{
set
{
_presenter = value;
_presenter.View = (TView) ((IView) this);
}
}
Я думаю, тебе нужно быть чем-то вроде FormData = ((IHasFormData<T>) (IMasterForm
)Page)).FormData;