我实现了一些依赖项(它们是MVP模式的一部分)。现在,当我尝试执行转换时,VS会通知错误。
说明:
interface IView
{
void setPresenter(IPresenter<IView> presenter);
}
interface IViewA : IView
{
}
interface IPresenter<T> where T : IView
{
void setView(T view);
}
class PresenterA : IPresenter<IViewA>
{
}
隐性演员:
IPresenter<IView> presenter = new PresenterA();
编译错误: 无法隐式将“PresenterA”类型转换为“IPresenter”。存在显式转换(您是否错过了演员?)
明确演员:
IPresenter<IView> presenter = (IPresenter<IView>)new PresenterA();
运行时错误:InvalidCastException
我如何解决它以保持这个概念?具有泛型类型的概念(我之前的一个没有它)。我已经尝试了其他帖子中提到的差异和逆转问题(进出),但也有错误(在VS 2010下)。
答案 0 :(得分:4)
IViewA
派生自IView
的事实并不自动意味着IPresenter<IViewA>
来自IPresenter<IView>
。事实上,IPresenter<IViewA>
和IPresenter<IView>
是两种不同的类型,它们之间没有继承关系。他们唯一的共同祖先是object
。
让我们看一个例子。假设我们有一个班级Animal
,一个来自Cat
的班级Animal
和一个源自Dog
的班级Animal
。现在让我们声明两个列表
List<Animal> animals;
List<Cat> cats = new List<Cat>();
我们还假设可以进行以下分配:
animals = cats;
animals.Add(new Cat()); // OK
animals.Add(new Dog()); // Ooops!
该列表实际上是一个猫列表,我们正在尝试添加一只狗!因此,List<Animal>
和List<Cat>
这两种类型不允许分配兼容。
答案 1 :(得分:2)
这个问题实际上是关于泛型类型的协方差和逆变。我们有
IViewA
是IView
但这并不意味着
IPresenter<IViewA>
是IPresenter<IView>
当结论成立时,我们会在IPresenter<T>
中说T
协变 。在C#中,通过在问题类型参数(此处为out
)之前放置T
关键字来使接口协变,如:
interface IPresenter<out T> ...
由于您未放置out
关键字,因此不允许执行此操作。
但如果T
的所有用途都“消失”,那么在T
中制作类型协变是唯一安全的。例如,可以使用T
作为返回类型的方法或get
的属性类型 - 仅限属性。
您的界面在“in”位置使用T
,即作为值参数(即不带ref
(或out
)修饰符的参数)。因此,使界面协变是违法的。请参阅其他一些答案,了解如果不存在此限制可能会发生什么。
然后是 逆转 的概念,IPresenter<>
表示
IViewA
是IView
意味着
IPresenter<IView>
是IPresenter<IViewA>
注意订单如何随逆变而变化。当在“in”位置使用type参数时,逆变量只是安全的(并且只允许),例如值参数。
基于唯一的成员,宣布你的界面逆变是很好的:
interface IPresenter<in T> ...
其中in
表示逆变,当然。但它会扭转允许隐式转换的“方向”。
答案 2 :(得分:1)
通过在PresenterA
中存储IPresenter<IView>
,您说“此对象的方法setView
接受任何IView
”。
但是,PresenterA
的方法setView
只接受IViewA
。如果你传递了IViewSomethingElse
,那么你期望会发生什么?
它不起作用,因此编译器不允许它。
答案 3 :(得分:0)
你想要做的事情没有意义。想象一下以下内容(包括一个有意义的方差):
interface IView {}
interface IViewA : IView {}
class ViewA : IViewA {}
interface IViewB : IView {}
class ViewB : IViewB {}
interface IPresenter<in T> where T : IView
{
void setView(T view);
}
class PresenterA : IPresenter<IViewA>
{
public void setView(IViewA view) {}
}
class PresenterB : IPresenter<IViewB>
{
public void setView(IViewA view) {}
}
现在,如果您尝试进行的转换有效,则可以执行此操作:
IPresenter<IView> presenter = new PresenterA();
presenter.setView(new ViewB());
如您所见,这不是类型安全的。即你认为这些类型之间存在的关系不存在。
您可以采用的方差相反:
class Presenter : IPresenter<IView>
{
public void setView(IView view) {}
}
IPresenter<IViewA> presenter = new Presenter();
Presenter.setView()
可以接受任何IView
参数,因此它可以接受IViewB
。这也是编译器提到显式转换的原因。这是让你做以下事情:
IPresenter<IViewA> presenterA = new Presenter();
IPresenter<IView> presenter = (IPresenter<IView>) presenterA;
也就是说,检查在运行时分配给presenter
的值是否恰好是“足够通用”的值,即使它的编译时类型不是。