Limited generics with hierarchy in type parameter

I have a problem with generics in C #. I hope you can help me.

public interface IElement { } public interface IProvider<T> where T : IElement { IEnumerable<T> Provide(); } 

So far, it's pretty simple. I want providers to return enumerations of certain elements. The specific implementation of the interfaces is as follows:

 public class MyElement : IElement { } public class MyProvider : IProvider<MyElement> { public IEnumerable<MyElement> Provide() { [...] } } 

But the problem arises when I want to use it. This does not compile because it cannot implicitly convert MyProvider to IProvider<IElement> :

 IProvider<IElement> provider = new MyProvider(); 

I need to broadcast IProvider<IElement> , despite the fact that MyProvider is IProvider<MyElement> and MyElement is IElement . I could avoid the cast by creating MyProvider also implement IProvider<MyElement> , but why doesn't it allow the hierarchy in the type parameter?

EDIT: As suggested by Thomas, we can make it covariant in T But what if there are other methods, like below, where are the arguments of type T ?

 public interface IProvider<T> where T : IElement { IEnumerable<T> Provide(); void Add(T t); } 
+6
source share
3 answers

If you use only the IProvider<IElement> link to access methods that have T in the output position, you can split the interface into two (find more suitable names for them, for example, ISink<in T> for contravariant)

 public interface IProviderOut<out T> where T : IElement { IEnumerable<T> Provide(); } public interface IProviderIn<in T> where T : IElement { void Add(T t); } 

Both are implemented in your class:

 public class MyProvider : IProviderOut<MyElement>, IProviderIn<MyElement> { public IEnumerable<MyElement> Provide() { ... } public void Add(MyElement t) { ... } } 

But now you are using a covariant interface when you need to speed up:

 IProviderOut<IElement> provider = new MyProvider(); 

In addition, 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> ... 
+2
source

I need to broadcast IProvider<IElement> , despite the fact that MyProvider is IProvider<MyElement> and MyElement is IElement . Why does this not allow 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 need to cast to IList<IAnimal> , despite the fact that MyList is IList<Giraffe> and Giraffe is IAnimal . Why does this not work?”

This does not work, because ... suppose it worked:

 m.Add(new Tiger()); 

m is a list of animals. You can add a tiger to the list of animals. But m is really MyList, and MyList can only contain giraffes! If we allow this, you can add the tiger to the list of giraffes.

This should fail because IList<T> has an Add method that accepts T. Now maybe your interface does not have methods that accept T. In this case, you can mark the interface as covariant and the compiler will check that the interface Really safe for dispersion and allows the use of dispersion.

+8
source

Since T appears only at the output position of 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 function requires C # 4. Read more on Covariance and contravariance in wagons .

+6
source

Source: https://habr.com/ru/post/907627/


All Articles