这是一个协方差问题吗?不确定是否砖墙

时间:2010-01-05 18:01:43

标签: c# .net generics .net-3.5 covariance

我编写了用于管理表单的ASP.NET页面。它们基于以下基类。

public abstract class FormPageBase<TInterface, TModel> : Page, IKeywordProvider 
        where TModel:ActiveRecordBase<MasterForm>, TInterface, new()
        where TInterface:IMasterForm
    {
        public TInterface FormData { get; set; }                   
     }

示例SubClass在这里:

public partial class PersonalDataFormPage : FormPageBase<IPersonalDataForm, PersonalDataForm>, IHasFormData<IPersonalDataForm>, IHasContact
    {
    }

下面我在页面上有一个usercontrol,我想从页面“使用”“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 个答案:

答案 0 :(得分:14)

首先让我看看能否更简洁地重申这个复杂的问题。您有一个通用接口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 :(得分:1)

我有一个非常相似的基页,这就是我定义我的方式。

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;

这样的东西

答案 2 :(得分:1)

4.0之前的C#要求所有强制类型转换为通用类型以完全匹配类型参数。 4.0引入了co-contra和contra-variance,但是你在早期版本中尝试执行的演员阵容是不可能的。