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>();
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
{
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)
{
}
}
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
{
}
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.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.