C# 转换继承的通用接口

Posted

技术标签:

【中文标题】C# 转换继承的通用接口【英文标题】:C# casting an inherited Generic interface 【发布时间】:2012-09-04 03:56:12 【问题描述】:

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

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

我基本上有这个工作,但这些演示者和他们管理的视图被捆绑到插件中,这些插件在运行后加载,这意味着我需要一个充当插件接口的 Manager 类,它带有一个“mode”参数。此模式参数用于工厂方法来创建添加或编辑演示器,但因为显示对话框的调用是稍后进行的,所以我需要通过 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 类型规范都可以告诉我是接口的子类,所以我不明白为什么它不会强制转换。

有什么想法吗?

史蒂夫

【问题讨论】:

【参考方案1】:

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

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

interface IPresenter<out V> where V : IView

    void PresBlah();


您可以通过以下 MSDN 文章了解有关其工作原理的更多信息:Covariance and Contravariance in Generics。 Generic Interfaces with Covariant Type Parameters 部分特别适用于您的问题。

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

【讨论】:

这仅在V 仅用于输出位置时才有效。例如,不能有 V 类型的方法参数或属性设置器。 @phoog,你是对的。这与 OP 发布的代码一致,并在参考文章中进行了详细解释。 但是 OP 的“简化”示例省略了 type 参数的所有用途。想必是类型参数在实际界面中somewhere使用的;它可能用于输入位置,因此接口可能不适合协方差。 @StephenYork,听起来你可能还想要一个 new 对 V 的约束 嗨,非常感谢您的解释和参考。我的设计理念是为 Presenter 提供它所呈现的表单类型,并将在内部使用 'view = new V();' 构造它然后它将通过各种方法将数据传递给视图,以填充控件、启用、执行表单验证等。我只需要在具体演示者中进行转换,因为稍后我需要调用接口方法,因此需要管理的工作更少。我会仔细阅读上面引用的文章,并尝试在早上开始工作。【参考方案2】:

CPresenter&lt;CView&gt; 不是IPresenter&lt;IView&gt;,就像List&lt;int[]&gt; 不是IList&lt;IEnumerable&gt;

考虑一下。如果你可以得到一个IList&lt;IEnumerable&gt;List&lt;int&gt; 的引用,你可以向它添加一个string[],这将不得不抛出一个异常。静态类型检查的重点是防止此类代码的编译。

如果接口允许,您可以将类型参数声明为协变 (IPresenter&lt;out V&gt; where V : ...。然后接口的行为将更像IEnumerable&lt;out T&gt;。这只有在类型参数从不用于输入位置时才有可能。

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

【讨论】:

感谢您提供信息。我不知道发生了什么,所以搜索协方差根本不在我的雷达上。尽管在我的界面中满足了“只有在输入位置从未使用类型参数的情况下才有可能”,但这是有道理的。 CPresenter 会在 IView 的实现内部调用 new 并在内部进行管理。

以上是关于C# 转换继承的通用接口的主要内容,如果未能解决你的问题,请参考以下文章

在C#中,一个类可以继承另一个类和一个接口吗?

为啥 C# 中的集合类(如 ArrayList)继承自多个接口,如果其中一个接口继承自其余接口?

C#中接口的基本概念

C#:私有内部接口是不是可能?

C++ 中的类接口继承

C#接口