How to "carry" covariance across multiple interfaces

I have an interface structure that looks like this:

At the most basic level, there is an IDataProducer with this definition:

public interface IDataProducer<out T>
{
    IEnumerable<T> GetRecords();
}

      

and IDataConsumer, which looks like this:

public interface IDataConsumer<out T>
{
    IDataProducer<T> Producer { set; }
}

      

Finally, I have an IWriter that comes from IDataConsumer as follows:

public interface IWriter<out T> : IDataConsumer<T>
{
    String FileToWriteTo { set; }

    void Start();
}

      

I wanted to make an Iwriter generic type T covariant so that I could implement a Factory method to create Writers that could handle different objects without knowing which type would be returned ahead of time. This was implemented by marking the generic type "out". The problem is that because of this I am getting a compilation error in IDataConsumer:

Invalid variance: The type parameter 'T' must be contravariantly valid on 'IDataConsumer<T>.Producer'. 'T' is covariant.

      

I'm not sure how this might be. It seems to me that the generic type is marked as covariant across the entire interface chain, but it is very possible that I do not fully understand how covariance works. Can someone explain to me what I am doing wrong?

+3


source to share


2 answers


The problem is that your property Producer

is write-only. That is, you are actually using in a T

contravariant way, passing a value that is generic in type T

to the executor of the interface, not the passing implementation.

One of the things I like the most about the way the C # language development team handled the variance function in common interfaces is that the keywords used to denote covariant and contravariant type parameters are mnemonic using parameters. I always have a hard time remembering what the words covariant and contravariant mean, but I never have to remember what out T

vs. means. in T

... The former means that you promise to only return values T

from the interface (such as method return values ​​or property attributes), while the latter means that you promise to only accept values T

in the interface (such as method parameters or property defactors).



You broke that promise by providing a setter for a property Producer

.

Depending on how these interfaces are implemented, you might want to interface IDataConsumer<in T>

. That would at least compile. :) And as long as the implementation IDataConsumer<T>

does only consume the values T

, it will probably work. It's hard to say without a more complete example.

+7


source


Peter's answer is correct. To add to that: it helps to try some examples and see what goes wrong. Let's assume the code you were originally allowed by the compiler. Then we could say:

class TigerConsumer : IDataConsumer<Tiger> 
{
    public IDataProducer<Tiger> p;
    public IDataProducer<Tiger> Producer { set { p = value; } }
    ... and so on ...
}
class GiraffeProducer : IDataProducer<Giraffe> 
{ 
  public IEnumerable<Giraffe> GetRecords() { 
    yield return new Giraffe(); 
  }

TigerConsumer t = new TigerConsumer();
IDataConsumer<Mammal> m = t;        // compatible with IDataConsumer<Mammal>
m.Producer = new GiraffeProducer(); // compatible with IDataProducer<Mammal>
foreach(Tiger tiger in t.p.GetRecords()) 
  // And we just cast a giraffe to tiger

      



Every step along the way here is completely typical, but the program is clearly wrong. Either one of these conversions must be illegal, or one of the interfaces is unsafe for covariance. We want all of these conversions to be legal, and therefore we must detect the lack of type safety in your interface declarations.

+7


source







All Articles