Introduction
Welcome to this installment of the .NET Nuts & Bolts
column! The focus of this article will be on safe covariance
and contravariance changes that will be released as a part
of the upcoming 4.0 version of the .NET
Framework and how this affects the .NET Developer. In
order to run the examples contained within this article
you’ll need to use an early preview such as the .NET Framework SDK 4.0 beta or CTP
release.
Invariance, covariance, and contravariance are all topics
you have likely been exposed to, but likely do not recognize
by name. Invariance means there must be an exact match for a
formal type and cannot be assigned a more or less derived
type. Covariant involves being able to use a more derived
type as a substitute, e.g. a string in place of an object.
Contravariance involves the ability to use a less derived
type as a substitute, e.g. an object in place of a string.
In many cases, the .NET framework supports covariant parameters and
contravarient return types. However, the game changes with
arrays and collections as they behave differently than other
types when it comes to covariance and contravariance. It is
very possible you have run in to this difference in
behavior. You likely worked around it, but were left
scratching your head at the difference in behaviors.
Covariance in Arrays and Collections
.NET arrays are covariant, which means a string array can
be passed as a less derived object array. However, .NET
string arrays are not safely covariant. Code involving
string arrays being used as a less derived type will
compile, but will fail at runtime. For arrays involving
reference types, every assignment has a dynamic type check
that checks what you are assigning is the same type as the
actual array. If the assignment involving a reference type
does not match it will generate an exception. However, the
compiler does nothing to keep you from making mistakes in
how you treat the objects.
string[] testStrings = {“Item1”, “Item2”, “Item3”, “Item4”, “Item5”};
Process (testStrings);void Process(object[] objects)
{
objects[0] = “Hello”; // Ok
objects[1] = new Button(); // Generates an exception
}
Generics
C# 2.0 introduced generics. When the example above is
written using generics it would fail at compile time because
generics are invariant. Invariant means the type only
matches itself, so different types are not a substitute for
the formal type. You cannot substitute either a more or less
derived type. For example, you would think a variable,
method declaration, and method call like the example below
would work because any collection that implements
IEnumerable<T> because any T would derive from object.
However, since Generics are invariant it will not work.
List<STRING> strings = GetStringList();
Process(strings);void Process(IEnumerable<OBJECT> objects)
{
// …
}
Safe Covariance and Contravariance
The .NET Framework 4.0 introduces some behavior changes
and reuses some language keywords (out and in) to put you in
control of covariance and contravariance. Out is used for
covariance, which means the types can be treated as less
derived. It is applied to output positions only. The example
below demonstrates how covariance is used to allow an
IEnumerable of strings to be treated as a lesser derived
IEnumerable of objects.
public interface IEnumerable<out T>
{
IEnumerator<T> GetEnumerator();
}public interface IEnumerator<out T>
{
T Current { get; }
bool MoveNext();
}// Types can be treated as less derived
IEnumerable<string> strings = GetStrings();
IEnumerable<object> objects = strings;
The in keyword is used for contravariance. The example
below demonstrates how the input position is used to treat
an array as a more derived type.
public interface IComparer<in T>
{
int Compare(T x, T y);
}// Types can be treated as more derived
IComparer<object> objComp = GetComparer();
IComparer<string> strComp = objComp;
Additionally, generics are no longer just invariant and
examples like the one above on generics will now behave as
you would originally expect in certain cases of covariance.
List<string> strings = GetStringList();
Process(strings);void Process(IEnumerable<object> objects)
{
// IEnumerable<T> is read-only and therefore safely covariant
}
Summary
You have learned about safe covariance and contravariance
being introduced as a part of the upcoming .NET Framework
4.0 release as well as changes in behavior with Generics.
For those that have run in to the need for it the changes
will be a welcome addition.
Future Columns
The topic of the next column is yet to be determined. It
will most certainly be something focused towards the next
release of the .NET Framework. If you have something in
particular that you would like to see explained here you
could reach me at mark
.strawmyer@crowehorwath.com.