C# 3.0 隐式强制转换错误,具有泛型类型的类和接口
本文关键字:泛型类型 接口 错误 转换 | 更新日期: 2023-09-27 17:58:28
我实现了一些依赖项(这是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();
编译错误:不能将类型"演示者 A"隐式转换为"IPresenter"。存在显式转换(您是否缺少强制转换?
显式投射:
IPresenter<IView> presenter = (IPresenter<IView>)new PresenterA();
运行时错误:无效强制转换异常
我怎样才能解决它以保持这种概念?具有泛型类型的概念(我以前的概念没有它(。我已经尝试了其他帖子中提到的方差和逆变问题(进出(,但也有错误(在VS 2010下(。
IViewA
派生自IView
的事实并不自动意味着IPresenter<IViewA>
派生自IPresenter<IView>
。事实上,IPresenter<IViewA>
和IPresenter<IView>
是两种不同的类型,它们之间没有继承关系。他们唯一的共同祖先是object
。
让我们看一个例子。假设我们有一个类Animal
,一个从Animal
派生的类Cat
和一个派生自Animal
的类Dog
。现在让我们声明两个列表
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>
两种类型与赋值兼容。
这个问题实际上是关于泛型类型的协方差和逆变。我们有
IViewA
就是IView
但这并不自动意味着
IPresenter<IViewA>
是一种IPresenter<IView>
当结论成立时,我们说IPresenter<T>
在T
是协变的。在 C# 中,通过在所讨论的类型参数(此处T
(之前放置一个 out
关键字来创建接口协变,如下所示:
interface IPresenter<out T> ...
由于您没有输入out
关键字,因此不允许执行的操作。
但是,只有在T
的所有用法都"消失"的情况下,在T
中生成类型协变才是安全的。例如,可以将T
用作方法的返回类型或仅get
属性的属性类型。
您的接口在"in"位置使用T
,即作为值参数(即没有ref
(或out
(修饰符的参数(。因此,使您的接口协变是非法的。请参阅其他一些答案,了解如果没有此限制会发生什么的示例。
然后是逆变的概念,对IPresenter<>
来说,这意味着
IViewA
是一种IView
暗示
IPresenter<IView>
是一种IPresenter<IViewA>
请注意顺序如何随逆变而变化。仅当类型参数用于"in"位置(例如值参数(时,逆变才是安全的(并且仅允许(。
基于唯一的成员,声明您的接口逆变将是合法的:
interface IPresenter<in T> ...
当然,in
的意思是逆变。但它将反转允许隐式转换的"方向"。
通过在IPresenter<IView>
中存储PresenterA
,你说"这个对象有一个接受任何IView
的方法setView
"。
但是,PresenterA
的方法setView
只接受IViewA
。如果你通过一个IViewSomethingElse
,你会期望会发生什么?
不起作用,因此编译器不允许它。
你想做的事情没有意义。想象一下以下内容(包括一个有意义的方差问题(:
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
的值是否恰好是"足够通用"的值,即使其编译时类型不是。