【问题标题】:Is this a covariance problem? Not sure if brick wall这是协方差问题吗?不知道是不是砖墙
【发布时间】:2011-01-01 18:11:23
【问题描述】:

我编写了管理表单的 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&lt;IMasterForm&gt; 它告诉我该页面是 IHasFormData&lt;IFormSubclass&gt;,即使我对 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

}

【问题讨论】:

  • Skeeter 在 c# 中深入讨论了这一点。值得一看。
  • 我确实想到了这一点。也许对于我自己的个人应用程序,我可能会为 lulz 做它

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


【解决方案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;

【讨论】:

  • 如果你不能以与此类似的方式投射它,那只能来自接口上的泛型,因为我的观点没有 T 类型。
  • 这与我的模式非常接近,谢谢你的这个例子。
【解决方案2】:

让我先看看我能否更简洁地重申这个复杂的问题。你有一个通用接口IHasFormData&lt;T&gt;。您有一个已知实现IHasFormData&lt;IFormSubclass&gt; 的对象。您希望将其转换为IHasFormData&lt;IMasterForm&gt;。你知道有一个从 IFormSubclass 到 IMasterForm 的引用转换。这失败了。

是吗?

如果这是对问题的正确陈述,那么是的,这是一个接口协方差问题。 C# 3 不支持接口协方差。 C# 4 会,如果你可以向编译器证明协方差是安全的。

让我简要地为您描述一下为什么这可能不安全。假设您有具有明显子类关系的 Apple、Orange 和 Fruit 类。您有一个 IList&lt;Apple&gt;,您想将其转换为 IList&lt;Fruit&gt;。这种协变转换在 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&lt;T&gt; 公开了一个将 T 作为参数的方法。为了让编译器允许在您的接口IHasFormData&lt;T&gt; 上进行协变转换,您必须向编译器证明IHasFormData&lt;T&gt; 没有公开任何将T 作为参数的内容。您可以通过声明接口IHasFormData&lt;out T&gt; 来做到这一点,这是一个助记符,意思是“T 只出现在输出位置”。然后编译器将验证您的声明是否正确,并开始允许协变转换。

有关 C# 4 中此功能的更多信息,请参阅我关于该功能设计的注释存档:

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

【讨论】:

  • Eric 是否会这样定义我的答案,因为我可以协变地使用 IView,因为它不是通用的,好像它是通用的,我无法按照我的方式进行转换?
  • 我想/很害怕。冒着一些重复的风险,在子类接口旁边公开与其基本接口相同的 IModel 是可恶的。我想我可以继续看看以后的重构。绝对目标是在许多不同的表单上使用通用的用户控件,我希望只是将页面中的数据“投射”到其基本界面中,因为这就是所有通用控件的需求。
  • 克里斯,我认为这就是明显的区别。我希望我的用户控件与他们模型的接口进行交互......它没有逻辑,但只是暴露了哑数据......然后页面本身只会采用该 IViewModel 并将其传递给控制器​​层,控制器层会将其转换为ActiveRecord 类型并调用 Save()...
  • 克里斯,我不明白这个问题。您所说的与非通用的东西“协变地工作”是什么意思?确切地说,您将哪种类型的投影描述为协变?
  • (specificView) ViewBasePage&lt;specificPresenter, specificView&gt; 的转换,集合内_presenter.View 所在行的外部转换。
【解决方案3】:

C# 4.0 之前的版本要求所有转换为泛型类型以完全匹配类型参数。 4.0 引入了协方差和反方差,但您尝试执行的演员表在早期版本中是不可能的。

【讨论】:

    猜你喜欢
    • 2014-09-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-27
    • 2019-07-17
    • 2023-03-05
    • 1970-01-01
    • 2020-04-15
    相关资源
    最近更新 更多