Search code examples
c#genericsinterfacecovariancecontravariance

Covariant interface with contravariant interface as member property


I have an interface which defines a reader and a writer for any IFoo.

public interface IFoobarStore<out E>
 where E : class, IFoobar
{
    IFoobarReader<E> GetReader();
    IFoobarWriter<E> GetWriter();
}

IFoobarStore is covariant. IFoobarStore interacts with any derived IFoo. As such, any more derived IFoo should be assignable to a more derived IFoo type argument.

// DerivedFoobityStore.cs
public sealed class DerivedFoobityStore
 : IFoobarStore<MyFoobity>
{
    // implementation follows
}

If IFoobarStore were defined as being variant with IFoobarStore<E> instead of IFoobarStore<out E>, the following would produce compiler error CS0266.

IFoobarStore<IFoo> myGenericStore = new DerivedFoobityStore();

The reader is defined as covariant as well. It should allow reading derived IFoo objects from somewhere.

using System.Collections.Generic;
public interface IFoobarReader<out E>
 where E : class, IFoo
{
    IEnumerable<E> GetAll();
    IEnumerable<E> GetBy(params object[] vars);
    E GetSingle(object uniqueIdentifier);
}

IFoobarWriter exposes members used for standard CRUD operations on any IFoo.

public interface IFoobarWriter<in E>
 where E : class, IFoo
{
    void Add(E foo);
    int Delete(E foo);
    E Update(E foo);
}

Since every operation has a single argument of type E (any class derived from IFoo), IFoobarWriter must be flagged as contravariant.

When I compile my code I receive this error:

Invalid variance: The type parameter 'E' must be contravariantly valid on 'IFoobarStore<E>.GetWriter()'. 'E' is covariant.

How can I better refactor this code so it compiles successfully?

For the moment I got around it by refactoring IFoobarWriter to work with an object instead of an IFoo.

public interface IFoobarWriter<out E>
 where E : class, IFoo
{
    void Add(object foo);
    int Delete(object foo);
    object Update(object foo);
}

This renders the basic premise of IFoobarWriter obsolete.


Solution

  • The solution was to remove E as an acceptable argument for instance member methods of IFoobarWriter.

    public interface IFoobarWriter<out E>
     where E : class, IFoo
    {
        void Add(IFoo foo);
        int Delete(IFoo foo);
        object Update(IFoo foo);
    }
    

    By having Add, Delete, and Update accept IFoo they effectively limit the types they can work on (as opposed to setting the argument to object) well enough for certain business requirements.

    Having the type parameter E for IFoobarWriter remain covariant allows it to remain a part of the IFoobarStore interface.