Click here to Skip to main content
15,886,713 members
Articles / Programming Languages / C#

Practical Usage of C# Covariance

Rate me:
Please Sign up or sign in to vote.
4.89/5 (2 votes)
27 Apr 2014CPOL2 min read 6.7K   8  
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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 to 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --