Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this a covariance problem? Not sure if brick wall

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

}
like image 658
Famous Nerd Avatar asked Dec 05 '22 04:12

Famous Nerd


2 Answers

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

like image 68
Eric Lippert Avatar answered Dec 25 '22 20:12

Eric Lippert


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.

like image 38
recursive Avatar answered Dec 25 '22 18:12

recursive