20

我在构思一个我想出的界面时遇到了一些麻烦。这是 C# Windows 窗体的 MVP 设计。我有一个在我的表单类上实现的 IView 类。还有一个 IPresenter,我将其派生为各种特定的 Presenter。每个 Presenter 将根据角色以不同方式管理 IView,例如打开对话框以使用 AddPresenter 输入一组新数据,而不是使用 EditPresenter 编辑现有数据,后者会将数据预加载到表单上。这些中的每一个都从 IPresenter 继承。我想这样使用代码:

AddPresenter<ConcreteView> pres = new AddPresenter<ConcreteView>();

我基本上有这个工作,但这些演示者和他们管理的视图被捆绑到插件中,这些插件在运行后加载,这意味着我需要一个充当插件接口的 Manager 类,它带有一个“mode”参数。此模式参数用于工厂方法来创建 Add 或 Edit Presenter,但因为显示对话框的调用是稍后进行的,所以我需要通过 IPresenter 接口进行调用,如下所示:

private IPresenter<IView> pres;
public ShowTheForm()
{
    pres.ShowDialog();
}

现在,当我将 AddPresenter 的具体实例化到“pres”成员时,我遇到了问题。这是我所拥有的简化版本:

interface IView
{
    void ViewBlah();
}

interface IPresenter<V> where V : IView
{
    void PresBlah();
}

class CView : IView
{
    public void ViewBlah()
    {        
    }
}

class CPresenter<T> : IPresenter<T> where T : IView
{
    public void PresBlah()
    {
    }
}

private void button3_Click(object sender, EventArgs e)
{
    CPresenter<CView> cpres = new CPresenter<CView>();
    IPresenter<IView> ipres = (IPresenter<IView>)cpres;
}

这是错误:

Unable to cast object of type 'CPresenter`1[MvpApp1.MainForm+CView]' to type 'IPresenter`1[MvpApp1.MainForm+IView]'.

我可以告诉我的 Presenter 和 Generic 类型规范都是接口的子类,所以我不明白为什么它不会强制转换。

有什么想法吗?

史蒂夫

4

2 回答 2

32

问题是泛型类型参数。如果您使接口参数协变,那么强制转换将起作用。

这是通过添加out关键字来完成的,如下所示:

interface IPresenter<out V> where V : IView
{
    void PresBlah();

}

您可以通过以下 MSDN 文章了解有关其工作原理的更多信息:泛型中的协变和逆变具有协变类型参数的通用接口部分特别适用于您的问题。

更新:确保你检查了@phoog 和我之间的评论。如果您的实际代码接受 aV作为输入,您将无法使其成为协变的。引用的文章和@phoog 的回答更详细地解释了这个案例。

于 2012-09-11T08:53:29.533 回答
10

CPresenter<CView>不是一个IPresenter<IView>,就像List<int[]>不是一个IList<IEnumerable>

想想看。如果您可以获得IList<IEnumerable>对 a 的引用List<int>,则可以向其中添加 a string[],这将不得不引发异常。静态类型检查的重点是防止此类代码的编译。

如果接口允许,您可以将类型参数声明为协变 ( IPresenter<out V> where V : ...。然后接口将表现得更像IEnumerable<out T>。这只有在类型参数从未在输入位置使用时才有可能。

回到List<int[]>示例,将其视为IEnumerable<IEnumerable>安全的,因为您不能向IEnumerable<T>引用添加任何内容;您只能从中读取内容,反过来,将 anint[]视为 是安全的IEnumerable,所以一切都很好。

于 2012-09-11T08:54:19.120 回答