C#转换继承的通用接口

Ste*_*ork 16 c# generics casting interface covariance

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

AddPresenter<ConcreteView> pres = new AddPresenter<ConcreteView>();
Run Code Online (Sandbox Code Playgroud)

我基本上有这个工作,但这些演示者和他们管理的视图被捆绑到运行时加载的插件,这意味着我需要一个作为插件接口的Manager类采用"模式"参数.此模式参数用于工厂方法以创建"添加"或"编辑演示者",但由于稍后会调用显示对话框,因此我需要通过IPresenter接口进行调用,如下所示:

private IPresenter<IView> pres;
public ShowTheForm()
{
    pres.ShowDialog();
}
Run Code Online (Sandbox Code Playgroud)

现在我遇到的问题是将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;
}
Run Code Online (Sandbox Code Playgroud)

这是错误:

Unable to cast object of type 'CPresenter`1[MvpApp1.MainForm+CView]' to type 'IPresenter`1[MvpApp1.MainForm+IView]'.
Run Code Online (Sandbox Code Playgroud)

我可以告诉Presenter和Generic类型规范是接口的子类,所以我无法理解为什么它不会转换.

有什么想法吗?

史蒂夫

sma*_*man 25

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

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

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

}
Run Code Online (Sandbox Code Playgroud)

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

更新:确保检查@phoog和我之间的评论.如果您的实际代码接受一个V输入,您将无法使其协变.引用的文章和@ phoog的答案进一步详细解释了这个案例.

  • 这仅在"V"仅用于输出位置时才有效.例如,可能没有类型为"V"的方法参数或属性设置器. (3认同)
  • @phoog,你说得对。这与 OP 发布的代码一致,并在参考文章中进行了彻底解释。 (2认同)
  • 但OP的"减少简化"示例已经省略了类型参数的所有用途.据推测,类型参数*在实际界面中****使用*; 它可能用于输入位置,因此界面可能不适合协方差. (2认同)

pho*_*oog 8

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

想一想.如果你可以获得IList<IEnumerable>对a 的引用List<int>,你可以添加一个string[],它必须抛出异常.静态类型检查的重点是防止编译这样的代码.

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

要返回的List<int[]>例子,它安全的把它当作一个IEnumerable<IEnumerable>,因为你不能添加任何的IEnumerable<T>参考; 你只能读出它的东西,反过来,把它int[]当成一个是安全的IEnumerable,所以一切都很好.