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<CView>
不是IPresenter<IView>
,就像List<int[]>
不是IList<IEnumerable>
。
考虑一下。如果你可以得到一个IList<IEnumerable>
对List<int>
的引用,你可以向它添加一个string[]
,这将不得不抛出一个异常。静态类型检查的重点是防止此类代码的编译。
如果接口允许,您可以将类型参数声明为协变 (IPresenter<out V> where V : ...
。然后接口的行为将更像IEnumerable<out T>
。这只有在类型参数从不用于输入位置时才有可能。
回到List<int[]>
的例子,将其视为IEnumerable<IEnumerable>
是安全的,因为您不能向IEnumerable<T>
引用添加任何内容;您只能从中读取内容,反过来,将int[]
视为IEnumerable
是安全的,所以一切都很好。
【讨论】:
感谢您提供信息。我不知道发生了什么,所以搜索协方差根本不在我的雷达上。尽管在我的界面中满足了“只有在输入位置从未使用类型参数的情况下才有可能”,但这是有道理的。 CPresenter 会在 IView 的实现内部调用 new 并在内部进行管理。以上是关于C# 转换继承的通用接口的主要内容,如果未能解决你的问题,请参考以下文章