Practical usage of C# covariance

Variance is a .NET 4.0 concept that allows implicit conversion between instances of generic types. In other words, it permits the corresponding C# compiler (same applies to Visual Basic) to perform implicit type conversions between instances of generic types whose parameters are in the same inheritance chain.

Consider two type definitions:

public class Vehicle {}
public class Bicycle: Vehicle{}

Using generic interfaces for illustration, a generic interface is covariant in T, if an instance instantiated with type T can be replaced with another instance instantiated with type T1 where T1 derives from T, without need of an explicit cast. IEnumerable<T> is covariant, means that an instance of IEnumerable<Vehicle> can be substituted with an instance of IEnumerable<Bicycles> without requiring an explicit cast.

In code:

IEnumerable<Vehicle> vehicles = new List<Vehicle>();
IEnumerable<Bicycle> bikes = new List<Bicycle>();

// covariance allows us to do
vehicles = bikes; 

This is one of those tricky concepts to wrap one’s head around, so in this blog post I give a practical usage of the covariance.

Consider an abstract class, called Parser created for the purpose of parsing files of a given extension. This abstract class will contain an abstract method that should be overridden with actual parsing logic. The class is given as follows:

public abstract class Parser
{
    // other code omitted for clarity
    public abstract ParseResult<IEntity, IParseContext> Parse(string resourcePath);
}

where the return type definition is defined as:

  public class ParseResult<T, TContext>
    {
        public ParseResult(IEnumerable<T> data, IList<ParseError> errors, TContext context) 
        {
            Context = context;
            Errors = errors;
            Data = data;
        }

        public IEnumerable<T> Data { get; private set; }
        public IList<ParseError> Errors { get; private set; }
        public TContext Context { get; private set; }       
    }

Next, consider a concrete implementation as follows:

public class PrnParser : Parser 
{
    private readonly IAipGateway _aipGateway;
    public PrnParser(IAipGateway aipGateway)
    {
       _aipGateway = aipGateway;
    }
 public override ParseResult<IEntity, IParseContext> Parse(string resourcePath)
        {
            var args = new PrnParseArguments(DateTime.UtcNow, _aipGateway, new BufferedTextFileReader(resourcePath));
            return Parse<MeterReading, PrnParseArguments>(args);
        }

       private void ParseResult<MeterReading, PrnParseArguments> Parse(PrnParseArguments args)
       {
           // do the parsing here
       }
}

If you attempt to compile this, the compiler will issue an error indicating that IParseResult<MeterReading, PrnParseArguments> cannot be converted to ParseResult<IEntity, IParseContext> even though the class MeterReading implements interface IEntity and class PrnParseArguments implements interface IParseContext.

In .NET 3.5 and earlier, one way to solve this problem is via explicit casting. So you would do something like this:

public class PrnParser : Parser 
{
    public override ParseResults<IEntity, IParseContext> Parse(string resourcePath)
        {
            var args = new PrnParseArguments(DateTime.UtcNow, _aipGateway, new BufferedTextFileReader(resourcePath));            
            var parsedResult = Parse<MeterReading, PrnParseArguments>(args);
            return  new ParseResults<IEntity, IParseContext>(parsedResult.Data.Cast<IEntity>(), parsedResult.Errors, parsedResult.Context);            
        }
}

However, starting in .NET 4.0 there is an easier way.

What you need to do is define a generic covariant interface and use this interface as the return type of the abstract method. In this context, a covariant interface will allow implementations of the Parse method to return concrete implementations of IParseResult without requiring that explicit cast.

Such an interface could look like this:

   public interface IParseResult<out T, out TContext>
    {
        IEnumerable<T> Data { get; }
        IList<ParseError> Errors { get;  }
        TContext Context { get;  }
        bool Success { get; }
    }

and then we modify ParseResult<T, TContext> to inherit from our new covariant interface IParseResult as follows:

   public class ParseResult<T, TContext> : IParseResult<T, TContext> 
        where T : IEntity 
    {
        // other code emitted for brevity
    }

Note the out parameters indicating that this is a covariant interface. Changing the signature of the abstract method to return our new covariant interface allows the C# compiler to be able to deduce the appropriate is-a relationship between ParseResult<MeterReading, PrnParseArgumets> and IParseResult<IEntity, IParseContext>

It is also easy enough to see the effect of not making this interface covariant by removing either or both out parameters from IParseResult<IEntity, IParseContext>. When you do this, the original compiler error message re-appears. Please note that there are additional criteria that must be satisfied for a generic interface can be made covariant such absence of public property setters on the generic type. For example, if properties IParseResult.Data and IParseResult.Context had public property setters, this interface could not have been made covariant.

In another blog post, I will capture a practical usage of contravariance.

Advertisements

2 thoughts on “Practical usage of C# covariance

  1. Phild

    Great article!
    The concept of covariance (base: animal, derived: cat ), from a purely OOP point of view, this is an odd concept. When using generics, animals and cats should be interchangable based on the SOLID design principles, but that’s not so in .NET.
    However, I agree with the implementation, since cats may have behaviors (methods, properties) that animals do not have. In that case treating an animal as a cat can have disastrous consequences. Aside: When using IEnumerable, then T is forced to have readonly behaviors, since the ‘out’ constraint ensures that no write behaviors exist for that T object.

    Contravariance has the opposite effect. I’m looking forward to your next article.

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s