I wrote ASP.NET pages which will manage forms. They’re based on the following base class.
public abstract class FormPageBase<TInterface, TModel> : Page, IKeywordProvider
where TModel:ActiveRecordBase<MasterForm>, TInterface, new()
where TInterface:IMasterForm
{
public TInterface FormData { get; set; }
}
And a sample SubClass is here:
public partial class PersonalDataFormPage : FormPageBase<IPersonalDataForm, PersonalDataForm>, IHasFormData<IPersonalDataForm>, IHasContact
{
}
Below I have a usercontrol on the page which I want to “consume” the “FormData” from the page so that it can read/write to it.
I then, have a more “common” user control that I want to have operate on the base Interface of all my form subclasses… IMasterForm
But when the usercontrol tries casting Page.FormData (having tried to cast page to IHasFormData<IMasterForm> it tells me that the page is IHasFormData<IFormSubclass> even though I have a constraint on the IFormSubclass that says it is also IMasterForm
Is there anyway that i can cast from the generic subclass to the generic superclass or is this “covariance” and a C# 4.0 thing?
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
}
Let me first see if I can restate this complicated problem more succinctly. You have a generic interface
IHasFormData<T>. You have an object which is known to implementIHasFormData<IFormSubclass>. You wish to convert it toIHasFormData<IMasterForm>. You know that there is a reference conversion from IFormSubclass to IMasterForm. This fails.Yes?
If that is a correct statement of the problem, then yes, this is a question of interface covariance. C# 3 does not support interface covariance. C# 4 will, if you can prove to the compiler that covariance is safe.
Let me describe for you briefly why this might not be safe. Suppose you have classes Apple, Orange and Fruit with the obvious subclassing relationships. You have an
IList<Apple>which you would like to cast toIList<Fruit>. That covariant conversion is not legal in C# 4 and cannot be legal because it is not safe. Suppose we allowed it. You could then do this:Notice that the problem is that
List<T>exposes a method that takes a T as an argument. In order for the compiler to allow covariant conversions on your interfaceIHasFormData<T>, you must prove to the compiler thatIHasFormData<T>exposes nothing that takes a T as an argument. You’ll do that by declaring the interfaceIHasFormData<out T>, a mnemonic meaning “T only appears in output positions”. The compiler will then verify that your claim is correct, and start allowing the covariant conversions.For more information on this feature in C# 4, see my archive of notes on the design of the feature:
http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx