I have a problem with generics in C# I hope you can help me out with.
public interface IElement { }
public interface IProvider<T> where T : IElement {
IEnumerable<T> Provide();
}
So far it's pretty simple. I want the providers to return enumerables of specific elements. A specific implementation of the interfaces is as follows:
public class MyElement : IElement { }
public class MyProvider : IProvider<MyElement> {
public IEnumerable<MyElement> Provide() {
[...]
}
}
But the problem comes now when I want to use it. This does not compile because it cannot implicitly convert MyProvider
into IProvider<IElement>
:
IProvider<IElement> provider = new MyProvider();
I have to do a cast to IProvider<IElement>
despite MyProvider
is an IProvider<MyElement>
and MyElement
is an IElement
. I could avoid the cast by making MyProvider
also implement IProvider<MyElement>
, but why does it not resolve the hierarchy in the type parameter?
EDIT: As per Thomas's suggestion, we can make it covariant in T
. But what if there are other methods like below where there are arguments of type T
?
public interface IProvider<T> where T : IElement {
IEnumerable<T> Provide();
void Add(T t);
}
I have to do a cast to
IProvider<IElement>
despiteMyProvider
is anIProvider<MyElement>
andMyElement
is anIElement
. Why does it not resolve the hierarchy in the type parameter?
This is a very frequently asked question. Consider the following equivalent problem:
interface IAnimal {}
class Tiger : IAnimal {}
class Giraffe : IAnimal {}
class MyList : IList<Giraffe> { ... }
...
IList<IAnimal> m = new MyList();
Now your question is: "I have to do a cast to IList<IAnimal>
despite the fact that MyList
is an IList<Giraffe>
and Giraffe
is an IAnimal
. Why does this not work?"
It does not work because... suppose it did work:
m.Add(new Tiger());
m is a list of animals. You can add a tiger to a list of animals. But m is really a MyList, and a MyList can only contain giraffes! If we allowed this then you could add a tiger into a list of giraffes .
This must fail because IList<T>
has an Add method that takes a T. Now, maybe your interface has no methods that takes a T. In that case, you can mark the interface as covariant , and the compiler will verify that the interface is truly safe for variance and allow the variance you want.
Since T
only appears in output position in your IProvider<T>
interface, you can make it covariant in T
:
public interface IProvider<out T> where T : IElement {
IEnumerable<T> Provide();
}
This will make this instruction legal:
IProvider<IElement> provider = new MyProvider();
This feature requires C# 4. Read Covariance and Contravariance in Generics for more details.
If you only use the reference to IProvider<IElement>
to access methods that have T
in an output position, you could segregate the interface into two (please find better names for them, like ISink<in T>
for the contravariant one):
public interface IProviderOut<out T> where T : IElement {
IEnumerable<T> Provide();
}
public interface IProviderIn<in T> where T : IElement {
void Add(T t);
}
Your class implements both:
public class MyProvider : IProviderOut<MyElement>, IProviderIn<MyElement> {
public IEnumerable<MyElement> Provide() {
...
}
public void Add(MyElement t) {
...
}
}
But now you use the covariant interface when you need to upcast:
IProviderOut<IElement> provider = new MyProvider();
Alternatively, your interface can inherit from both:
public interface IProvider<T> : IProviderIn<T>, IProviderOut<T>
where T : IElement {
// you can add invariant methods here...
}
And your class implements it:
public class MyProvider : IProvider<MyElement> ...
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.