Isso é um problema de covariância? Não tenho certeza se a parede de tijolos
-
18-09-2019 - |
Pergunta
Eu escrevi páginas do ASP.NET que gerenciarão formulários. Eles são baseados na seguinte classe base.
public abstract class FormPageBase<TInterface, TModel> : Page, IKeywordProvider
where TModel:ActiveRecordBase<MasterForm>, TInterface, new()
where TInterface:IMasterForm
{
public TInterface FormData { get; set; }
}
E uma amostra de subclasse está aqui:
public partial class PersonalDataFormPage : FormPageBase<IPersonalDataForm, PersonalDataForm>, IHasFormData<IPersonalDataForm>, IHasContact
{
}
Abaixo, tenho um UserControl na página que eu quero "consumir" o "formData" da página para que possa ler/gravar.
Então, tenho um controle de usuário mais "comum" que quero ter operar na interface base de todas as minhas subclasses de formulário ... iMasterForm
Mas quando o UserControl tenta a página de fundição.FormData (tendo tentado lançar a página para IHasFormData<IMasterForm>
me diz que a página é IHasFormData<IFormSubclass>
Mesmo que eu tenha uma restrição no iFormSubclass que diz que também é o iMasterForm
Existe alguma maneira de que eu possa lançar da subclasse genérica para a superclasse genérica ou essa "covariância" e uma coisa 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
}
Solução
Deixe -me primeiro ver se consigo reafirmar esse problema complicado de maneira mais sucinta. Você tem uma interface genérica IHasFormData<T>
. Você tem um objeto que é conhecido por implementar IHasFormData<IFormSubclass>
. Você deseja convertê -lo para IHasFormData<IMasterForm>
. Você sabe que existe uma conversão de referência do IFORMSubClass para o iMasterForm. Isso falha.
Sim?
Se essa é uma declaração correta do problema, então sim, isso é uma questão de covariância da interface. C# 3 não suporta covariância da interface. C# 4 Will, Se você pode provar ao compilador que a covariância é segura.
Deixe -me descrever para você brevemente por que isso pode não estar seguro. Suponha que você tenha aulas de maçã, laranja e frutas com os óbvios relacionamentos de subclasse. Você tem um IList<Apple>
que você gostaria de lançar para IList<Fruit>
. Essa conversão covariante não é legal no C# 4 e não pode ser legal porque não é segura. Suponha que permitimos isso. Você poderia fazer isso:
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.
Observe que o problema é que List<T>
expõe um método que toma um argumento. Para que o compilador permita conversões covariantes em sua interface IHasFormData<T>
, você deve provar ao compilador que IHasFormData<T>
não expõe nada que tome um T como argumento. Você fará isso declarando a interface IHasFormData<out T>
, um significado mnemônico "t aparece apenas em posições de saída". O compilador verificará se sua reivindicação está correta e começará a permitir as conversões covariantes.
Para obter mais informações sobre esse recurso no C# 4, consulte meu arquivo de notas sobre o design do recurso:
http://blogs.msdn.com/ericlippert/archive/tags/covarince+and+contravariance/default.aspx
Outras dicas
C# Antes de 4.0, requer todos os elencos a tipos genéricos para corresponder exatamente ao parâmetro de tipo. 4.0 Introduz co-variação, mas o elenco que você está tentando executar é impossível nas versões anteriores.
Eu tenho uma página base muito semelhante à sua, é assim que defino o meu.
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);
}
}
Eu acho que você precisa ser algo como FormData = ((IHasFormData<T>) (IMasterForm
)Page)).FormData;