我编写了用于管理表单的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
}
答案 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,但是你在早期版本中尝试执行的演员阵容是不可能的。