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 anIView
but that does not automatically mean that
an
IPresenter<IViewA>
is anIPresenter<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 anIView
implies that
an
IPresenter<IView>
is anIPresenter<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.