Это проблема ковариации? Не уверен, что кирпичная стена - PullRequest
4 голосов
/ 05 января 2010

Я написал страницы 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

Но когда usercontrol пытается привести 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

}

Ответы [ 3 ]

14 голосов
/ 05 января 2010

Позвольте мне сначала посмотреть, смогу ли я сформулировать эту сложную проблему более кратко. У вас есть общий интерфейс IHasFormData<T>. У вас есть объект, который, как известно, реализует IHasFormData<IFormSubclass>. Вы хотите конвертировать его в IHasFormData<IMasterForm>. Вы знаете, что существует преобразование ссылок из IFormSubclass в IMasterForm. Это не удается.

Да

Если это правильное изложение проблемы, то да, это вопрос ковариации интерфейса. C # 3 не поддерживает ковариацию интерфейса. C # 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

1 голос
/ 05 января 2010

C # до 4.0 требует, чтобы все приведения к универсальным типам точно соответствовали параметру типа. 4.0 вводит ко-и противо-дисперсию, но приведение, которое вы пытаетесь выполнить, невозможно в более ранних версиях.

1 голос
/ 05 января 2010

У меня очень похожа базовая страница на вашу, вот как я определяю свою.

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;

...