简体   繁体   中英

C# 3.0 implicit cast error with class and interfaces with generic type

I implemented a few dependencies (which are the part of MVP pattern). Now, when I try to perform casting, VS informs about an error.

Definitions:

interface IView
{
     void setPresenter(IPresenter<IView> presenter);
}

interface IViewA : IView
{
}

interface IPresenter<T> where T : IView
{
    void setView(T view);
}

class PresenterA : IPresenter<IViewA>
{
}

Implicit cast:

IPresenter<IView> presenter = new PresenterA();

Compile error: Cannot implicitly convert type 'PresenterA' to 'IPresenter'. An explicit conversion exists (are you missing a cast?)

Explicit cast:

IPresenter<IView> presenter = (IPresenter<IView>)new PresenterA();

Run-time error: InvalidCastException

How can I solve it to keep this conception? Conception with generic type (my previous one is without it). I've tried variance and contravariance issue (in and out) as mentioned in the others posts, but there was errors too (under VS 2010).

The fact that IViewA derives from IView does not automatically mean that IPresenter<IViewA> derives from IPresenter<IView> . In fact IPresenter<IViewA> and IPresenter<IView> are two distinct types that do not have an inheritance relation between them. Their only common ancestor is object .

Let's look at an example. Assume that we have a class Animal , a class Cat deriving from Animal and a class Dog deriving from Animal . Now let's declare two lists

List<Animal> animals;
List<Cat> cats = new List<Cat>();

And let's also assume that the following assignment was possible:

animals = cats;
animals.Add(new Cat()); // OK
animals.Add(new Dog()); // Ooops!

The list is in reality a cats list and we are trying to add a dog! Therefore the two types List<Animal> and List<Cat> are not allowed to be assignment compatible.

This question is really about covariance and contravariance of generic types. We have

an IViewA is an IView

but that does not automatically mean that

an IPresenter<IViewA> is an IPresenter<IView>

When the conclusion holds, we say that IPresenter<T> is covariant in T . In C# one makes an interface covariant by putting an out keyword before the type parameter in question (here T ), as in:

interface IPresenter<out T>  ...

Since you didn't put the out keyword, what you do is not allowed.

But it is only safe to make a type covariant in T if all the uses of T goes "out". For example it is okay to use T as return type of methods or as property type for get -only properties.

Your interface uses T in an "in" position, namely as a value parameter (ie a parameter without the ref (or out ) modifier). So it would be illegal to make your interface covariant. See some of the other answers for examples of what could happen if this restriction wasn't there.

Then there's the notion of contravariance , which for IPresenter<> means that

an IViewA is an IView

implies that

an IPresenter<IView> is an IPresenter<IViewA>

Notice how the order changes with contravariance. Contravariance is only safe (and only allowed) when the type parameter is used in "in" positions, such as value parameters.

Based on the only member, it would be legat to declare your interface contravariant:

interface IPresenter<in T>  ...

where in means contravariant, of course. But it will reverse the "direction" in which implicit conversions are allowed.

By storing a PresenterA in an IPresenter<IView> , you're saying "this object has a method setView which accepts any IView ".

However, PresenterA 's method setView only accepts an IViewA . If you passed it an IViewSomethingElse , what would you expect to happen?

It won't work, so the compiler doesn't allow it.

What you're trying to do doesn't make sense. Imagine the following (including the one variance thingie that makes sense):

interface IView {}

interface IViewA : IView {}
class ViewA : IViewA {}

interface IViewB : IView {}
class ViewB : IViewB {}

interface IPresenter<in T> where T : IView
{
    void setView(T view);
}

class PresenterA : IPresenter<IViewA>
{
    public void setView(IViewA view) {}
}

class PresenterB : IPresenter<IViewB>
{
    public void setView(IViewA view) {}
}

Now, if the conversion you're trying to do were valid, you could do this:

IPresenter<IView> presenter = new PresenterA();
presenter.setView(new ViewB());

As you can see, this isn't typesafe. Ie the relationship you believe exists between the types isn't there.

What variance lets you do is the opposite:

class Presenter : IPresenter<IView>
{
    public void setView(IView view) {}
}

IPresenter<IViewA> presenter = new Presenter();

Presenter.setView() can accept any IView parameter, therefore it can accept an IViewB . This is also why the compiler mentions an explicit conversion. It's to let you do the following:

IPresenter<IViewA> presenterA = new Presenter();
IPresenter<IView> presenter = (IPresenter<IView>) presenterA;

That is, to check if the value you're assigning to presenter at runtime happens to be one that's "generic enough", even if its compile-time type isn't.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM