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下(。

C# 3.0 隐式强制转换错误,具有泛型类型的类和接口

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的值是否恰好是"足够通用"的值,即使其编译时类型不是。