C#3.0隐式转换错误与类和接口与泛型类型

时间:2013-02-08 18:53:47

标签: c# oop generics casting

我实现了一些依赖项(它们是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下)。

4 个答案:

答案 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)

这个问题实际上是关于泛型类型的协方差和逆变。我们有

  

IViewAIView

但这并不意味着

  

IPresenter<IViewA>IPresenter<IView>

结论成立时,我们会在IPresenter<T>中说T 协变 。在C#中,通过在问题类型参数(此处为out)之前放置T关键字来使接口协变,如:

interface IPresenter<out T>  ...

由于您未放置out关键字,因此不允许执行此操作。

但如果T的所有用途都“消失”,那么在T中制作类型协变是唯一安全的。例如,可以使用T作为返回类型的方法或get的属性类型 - 仅限属性。

您的界面在“in”位置使用T,即作为值参数(即不带ref(或out)修饰符的参数)。因此,使界面协变是违法的。请参阅其他一些答案,了解如果不存在此限制可能会发生什么。

然后是 逆转 的概念,IPresenter<>表示

  

IViewAIView

意味着

  

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