C# casting an inherited Generic interface

◇◆丶佛笑我妖孽 提交于 2019-12-17 16:18:16

问题


I'm having some trouble getting my head around casting an interface I've come up with. It's an MVP design for C# Windows Forms. I have an IView class which I implement on my form classes. There's also an IPresenter which I derive into various specific Presenters. Each Presenter will manage the IView differently depending on the role, for example opening the dialog to enter a new set of data with an AddPresenter as opposed to editing existing data with an EditPresenter which would preload data onto the form. Each of these inherit from IPresenter. I want to use the code as such:

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

I basically have this working but these presenters and the views they manage are bundled into plugins which are loaded post runtime which means I need a Manager class which acts as the plugin interface take a "mode" parameter. This mode parameter is use for a factory method to create either the Add or Edit Presenter, but because the call to show the dialog is made later on then I need to make the call via the IPresenter interface like so:

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

Now I'm having issues when it comes to casing my concrete instantiation of an AddPresenter say, to the 'pres' member. Here's a cut down simplified version of what I have:

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;
}

This is the error:

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

Both the Presenter and the Generic type specification from what I can tell ARE subclasses of the interfaces so I can't understand why it won't cast.

Any thoughts?

Steve


回答1:


The problem is the generic type parameter. If you make the interface parameter covariant then the cast will work.

This is accomplished by adding the out keyword, like so:

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

}

You can learn more about how this works with the following MSDN article: Covariance and Contravariance in Generics. The section Generic Interfaces with Covariant Type Parameters specifically applies to your question.

Update: Make sure you check the comments between @phoog and me. If your actual code accepts a V as an input, you will be unable to make it covariant. The referenced article and @phoog's answer explains this case in further detail.




回答2:


CPresenter<CView> is not an IPresenter<IView>, just as List<int[]> is not an IList<IEnumerable>.

Think about it. If you could get an IList<IEnumerable> reference to a List<int>, you could add a string[] to it, which would have to throw an exception. The whole point of static type checking is to prevent the compilation of such code.

If the interface allows, you could declare the type parameter as covariant (IPresenter<out V> where V : .... Then the interface would behave more like IEnumerable<out T>. This is only possible if the type parameter is never used in an input position.

To go back to the List<int[]> example, it is safe to treat it as an IEnumerable<IEnumerable>, because you can't add anything to an IEnumerable<T> reference; you can only read things out of it, and, in turn, it is safe to treat an int[] as an IEnumerable, so all is well.



来源:https://stackoverflow.com/questions/12365993/c-sharp-casting-an-inherited-generic-interface

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!