A couple of years ago, while working on a project, I got a compilation exception related to Generics.
I had a base class BaseVO
and classes deriving from BaseVO
. For example, “FlightVO : BaseVO
”. My idealistic assumption was that since: “FlightVO” inherits from BaseVO, and since BaseVO b = new FlightVO() is valid as per inheritance, then:
“List<BaseVO> lstBaseVO = new List<FlightVO>()
would also be valid.”
But duh !!! Instead, I get a compilation exception that says:
Argument ’1′: cannot convert from
‘System.Collections.Generic.List<ValueObjects.FlightVO>’
to ‘System.Collections.Generic.List<ValueObjects.BaseVO>’
Since I was so sure about my assumption being right, I posted it on the MSDN forum just to confirm that it was a “bug” :-) in Generics. Thread in MSDN forum: http://social.msdn.microsoft.com/Forums/bg-BG/netfxbcl/thread/e4863bda-3f24-4a28-af55-230baaff5e7b.
Those were the .NET 3.0 days and one of the MVP moderators gave a pretty good explanation as to why my assumption was wrong. Here’s his explanation:
“List<Base>
and List<Derived>
are distinct types, you can’t convert List<Derived>
to List<Base>
. If it were possible, you could pass a List<Derived>
to a method accepting List<Base>
and that method could add an object of type Base
to the list, which violates the promise that the original list only contains objects of type Derived
”.
I assume someone in the CLR team might have found my assumption quite viable and it got shape in .NET 4.0 as “Covariance and Contravariance in Generics” .
So the above case is a valid example where “Covariance in Generics” can be used.
However, if we compile List<BaseVO> lstBaseVO = new List<FlightVO>()
in .NET 4.0, it would still give the same compilation error for the very explanation given by our MVP friend. In order to make it covariant compliant, our modified piece of code would look like:
IEnumerable< BaseVO > lstBaseVO = new List< FlightVO >();
The above statement is valid because, in .NET 4.0, List<T> : IEnumerable<out T>
where type ‘T’ is marked as covariant.
Here’s a quick summarization about variance in Generics.
- What are the programming constructs where covariance and contravariance are applicable? Variant type parameters are restricted to generic interfaces and generic delegate types thereby ensuring type safety.
- In what scenarios would we want to use variance in Generics? Let's look at a code snippet.
So, covariance and contravariance of generic type parameters enable us to use constructed generic types [A<Athlete>] whose type arguments [Athlete] are more derived (covariance) or less derived (contravariance) than the type argument [Person] of a target constructed type [ISample<Person>].
Covariance seems very natural because it behaves like polymorphism, whereas Contravariance seems counterintuitive. One thing that we need to be clear about is that both Covariance and Contravariance both adhere to the thumb rule of “Inheritance” which says,
“A derived class instance can be assigned to a base class variable”
Now, let’s look at a Contravariance example.
Line 2 of code snippet might make you feel that the thumb rule of inheritance is being violated, i.e., the derived class variable d
is being assigned an instance of the Base
class.
However, that isn’t the case. Let’s understand each line of the code snippet.
In general, a covariant type parameter can be used as the return type of a generic delegate/interface, and contravariant type parameters can be used as input parameter types.
A brief summary of facts about variance in the common language runtime:
- A generic interface or generic delegate type can have both covariant and contravariant type parameters.
- Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type.
- How do we define variant generic interfaces and delegates?
Starting with the .NET Framework 4, Visual Basic and C# have keywords that enable you to mark the generic type parameters of interfaces and delegates as covariant or contravariant.
- A covariant type parameter is marked with the
out
keyword. You cannot use a covariant type parameter as a generic type constraint for interface
methods. E.g., T Display<out T>();
is an invalid statement.
- A contravariant type parameter is marked with the
in
keyword. You can use a contravariant type parameter as a generic type constraint for an interface
method.
Thank you for reading!