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 implement IHasFormData<IFormSubclass>
. You wish to convert it to IHasFormData<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 to IList<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:
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.
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 interface IHasFormData<T>
, you must prove to the compiler that IHasFormData<T>
exposes nothing that takes a T as an argument. You'll do that by declaring the interface IHasFormData<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
C# prior to 4.0 requires all casts to generic types to match the type parameter exactly. 4.0 introduces co- and contra-variance, but the cast you are trying to perform is impossible in earlier versions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With