Search code examples
c#.netgenericscovariancecontravariance

C# covariance issue with two covariant interfaces


I have two interfaces that are both covariant, with both being passed in to each other like so:

public interface Perfomer<in T>
{
    void Perform(T t, Tracer<T> tracer);
}

public interface Tracer<in T>
{
    void Notify();
}

However even though both interfaces are marked covariant, and T is only ever being used as input, I'm still getting the error:

"Invalid variance: The type parameter 'T' must be covariantly valid on
'Perfomer<T>.Do(T, Tracer<T>)'. 'T' is contravariant. [_Console].

Any ideas why having covariant interface parameter using the same type makes T contravariant?


Edit (Sorry, I am new to StackOverflow, based on the answers I realize I should've been more exact in my question, I had just tried to eliminate as much noise as possible to a single error).

The code actually has two interfaces with generally similar interfaces:

public interface Performer<in T>
{
    bool Perform(T t, Tracer<T> tracer = null);
}

public interface Tracer<in T>
{
    void Notify(Performer<T> performer, T t, ref bool success);
}

It's purpose is to allow the an optional "tracer" to see things happen/modify the results of a performer.


Solution

  • When you declare that Performer is contravariant, you are declaring that anything a Performer does to a T can also be done to a more specific version of T. For example, an action that acts on a object can be given a string, and it'll just act as if that string is an object.

    So for example you could do this, because all streams support Length:

    class MyClass : Performer<Stream>
    {
        void Perform(Stream t)
        {
            Console.WriteLine(t.Length)
        }
    }
    Performer<FileStream> p = new MyClass();
    p.Perform(new FileStream());
    

    But you can't do this, because you gave it a class that doesn't support IsAsync:

    class MyClass : Performer<FileStream>
    {
        void Perform(Stream t)
        {
            Console.WriteLine(t.IsAsync)
        }
    }
    Performer<Stream> p = new MyClass();
    p.Perform(new Stream());  //Stream isn't good enough; it has to be a FileStream, since it needs IsAsync
    

    So far so good. Now let's add in that second parameter:

    class MyClass : Performer<Stream>
    {
        void Perform(Stream t, Tracer<Stream> tracer)
        {
            Console.WriteLine(tracer.Notify())
        }
    }
    

    In order for this to work, the contravariance has to work. If the contravariance works, it means that Perform can store a Tracer<FileStream> (which you pass in) in a variable that is typed as a Tracer<Stream> (which is how it is implemented). That means that Tracer must be covariant with respect to its type argument.

    So you can fix your code by changing in to out, like so:

    public interface Performer<in T> 
    {
        void Perform(T t, Tracer<T> tracer);  
    }
    
    public interface Tracer<out T> //out instead of in
    {
        void Notify();
    }