Est-ce un problème de covariance? Je ne sais pas si le mur de briques
-
18-09-2019 - |
Question
J'ai écrit des pages ASP.NET qui va gérer les formulaires. Ils sont basés sur la classe de base suivante.
public abstract class FormPageBase<TInterface, TModel> : Page, IKeywordProvider
where TModel:ActiveRecordBase<MasterForm>, TInterface, new()
where TInterface:IMasterForm
{
public TInterface FormData { get; set; }
}
Et un échantillon est ici SubClass:
public partial class PersonalDataFormPage : FormPageBase<IPersonalDataForm, PersonalDataForm>, IHasFormData<IPersonalDataForm>, IHasContact
{
}
Ci-dessous j'ai un usercontrol sur la page que je veux « consommer » le « FormData » de la page afin qu'elle puisse lire / écrire.
Je puis, un contrôle plus utilisateur « commun » que je veux avoir fonctionner sur la base d'interface de toutes mes sous-classes de forme ... IMasterForm
Mais quand le usercontrol essaie casting Page.FormData (ayant essayé de jeter page il me dit IHasFormData<IMasterForm>
que la page est IHasFormData<IFormSubclass>
même si j'ai une contrainte sur la IFormSubclass qui dit qu'il est aussi IMasterForm
Y at-il de toute façon que je peux jetai de la sous-classe générique à la superclasse générique ou est-ce « covariance » et une chose 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
}
La solution
Permettez-moi tout d'abord voir si je peux reformuler ce problème compliqué plus succinctement. Vous disposez d'un IHasFormData<T>
d'interface générique. Vous avez un objet qui est connu pour mettre en œuvre IHasFormData<IFormSubclass>
. Vous souhaitez convertir en IHasFormData<IMasterForm>
. Vous savez qu'il ya une conversion de référence de IFormSubclass à IMasterForm. Cela échoue.
Oui?
Si tel est une déclaration correcte du problème, alors oui, cela est une question de covariance d'interface. C # 3 ne prend pas en charge covariance d'interface. C # 4 sera, si vous pouvez prouver au compilateur que covariance est sûr.
Permettez-moi de vous décrire brièvement pourquoi cela pourrait ne pas être en sécurité. Supposons que vous ayez des classes d'Apple, Orange et fruits avec les relations de sous-classement évidentes. Vous avez un IList<Apple>
que vous souhaitez jeter à IList<Fruit>
. Cette conversion covariante est pas légal en C # 4 et ne peut pas être légale, car il est pas sûr. Supposons que nous le permettait. Vous pouvez ensuite faire ceci:
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.
Notez que le problème est que List<T>
expose une méthode qui prend un T comme argument. Pour que le compilateur pour permettre des conversions covariants sur votre IHasFormData<T>
d'interface, vous devez prouver au compilateur que IHasFormData<T>
expose rien qui prend un T comme argument. Vous allez le faire en déclarant la IHasFormData<out T>
d'interface, un sens mnémonique « T apparaît uniquement dans des positions de sortie ». Le compilateur vérifiera ensuite que votre demande est correcte, et commencer à permettre aux conversions covariants.
Pour plus d'informations sur cette fonction en C # 4, voir mes archives de notes sur la conception de la fonction:
Autres conseils
C # avant 4.0 exige que tous les types génériques moulages pour correspondre au paramètre de type exactement. 4.0 introduit co- et contre-variance, mais le casting que vous essayez d'exécuter est impossible dans les versions antérieures.
J'ai une page de base très similaire à la vôtre est ce que je le mien définir.
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);
}
}
Je pense que vous devez être quelque chose comme FormData = ((IHasFormData<T>) (IMasterForm
)Page)).FormData;