이것이 공분산 문제입니까? 벽돌 벽이 확실하지 않습니다
-
18-09-2019 - |
문제
양식을 관리 할 ASP.NET 페이지를 작성했습니다. 그들은 다음 기본 클래스를 기반으로합니다.
public abstract class FormPageBase<TInterface, TModel> : Page, IKeywordProvider
where TModel:ActiveRecordBase<MasterForm>, TInterface, new()
where TInterface:IMasterForm
{
public TInterface FormData { get; set; }
}
샘플 서브 클래스가 여기에 있습니다.
public partial class PersonalDataFormPage : FormPageBase<IPersonalDataForm, PersonalDataForm>, IHasFormData<IPersonalDataForm>, IHasContact
{
}
아래에는 페이지에서 "formdata"를 "소비"하여 읽거나 쓸 수 있도록 USERCONTROL이 있습니다.
그런 다음 모든 양식 서브 클래스의 기본 인터페이스에서 작동하고 싶은 "일반적인"사용자 컨트롤이 있습니다 ... Imasterform
그러나 UserControl이 캐스팅 페이지를 시도 할 때 (페이지를 주조하려고 시도했을 때 IHasFormData<IMasterForm>
페이지가 있다고 알려줍니다 IHasFormData<IFormSubclass>
iformsubclass에 제약이 있어도 imasterform이라는 제약이 있습니다.
어쨌든 제네릭 서브 클래스에서 제네릭 슈퍼 클래스로 캐스트 할 수 있는가?
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
}
해결책
이 복잡한 문제를 더 간결하게 재현 할 수 있는지 먼저 살펴 보겠습니다. 일반적인 인터페이스가 있습니다 IHasFormData<T>
. 구현하는 것으로 알려진 객체가 있습니다 IHasFormData<IFormSubclass>
. 당신은 그것을 변환하고 싶습니다 IHasFormData<IMasterForm>
. iformsubclass에서 imasterform으로 참조 변환이 있음을 알고 있습니다. 이것은 실패합니다.
예?
그것이 문제에 대한 올바른 진술이라면, 그렇습니다. 이것은 인터페이스 공분산의 문제입니다. C# 3은 인터페이스 공분산을 지원하지 않습니다. C# 4 Will, 컴파일러에게 공분산이 안전하다는 것을 증명할 수 있다면.
왜 이것이 안전하지 않은지 간단히 설명하겠습니다. 분명한 서브 클래스 관계가있는 사과, 오렌지, 과일 수업이 있다고 가정 해 봅시다. 당신은 있습니다 IList<Apple>
캐스팅하고 싶은 IList<Fruit>
. 이 공변량 전환은 C# 4에서 합법적이지 않으며 안전하지 않기 때문에 합법적 일 수 없습니다. 우리가 그것을 허용했다고 가정 해 봅시다. 그런 다음 다음을 수행 할 수 있습니다.
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.
문제는 그 사실입니다 List<T>
T를 인수로 취하는 메소드를 노출시킵니다. 컴파일러가 인터페이스에서 공분산 변환을 허용하려면 IHasFormData<T>
, 당신은 컴파일러에게 그것을 증명해야합니다 IHasFormData<T>
t를 논쟁으로 취하는 것은 아무것도 드러납니다. 인터페이스를 선언하여 그렇게 할 것입니다 IHasFormData<out T>
, "t는 출력 위치에만 나타난다"는 니모닉을 의미합니다. 그런 다음 컴파일러는 귀하의 주장이 올바른지 확인하고 공분산 변환을 허용합니다.
C# 4 의이 기능에 대한 자세한 내용은 기능 디자인에 대한 메모 아카이브를 참조하십시오.
http://blogs.msdn.com/ericlippert/archive/tags/covariance+and+contravariance/default.aspx
다른 팁
4.0 이전의 C#은 유형 매개 변수와 정확히 일치하도록 모든 캐스트를 일반 유형으로 요구합니다. 4.0은 공동 및 콘트라 분산을 소개하지만, 당신이 수행하려는 캐스트는 이전 버전에서는 불가능합니다.
나는 당신과 매우 비슷한 기본 페이지를 가지고 있습니다. 이것이 내가 내 것을 정의하는 방법입니다.
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);
}
}
나는 당신이 같은 것이 필요하다고 생각합니다 FormData = ((IHasFormData<T>) (IMasterForm
)Page)).FormData;