An aspect of generics that often comes across as surprising
is that the following is illegal:
IList<string> strings = new List<string>();
IList<object> objects = strings;
The second assignment is disallowed because strings does not have the same element type as objects. There is a perfectly good reason for
this. If it were allowed you could write:
objects[0] = 5;
string s = strings[0];
Allowing an int to be
inserted into a list of strings and
subsequently extracted as a string. This
would be a breach of type safety.
However, there are certain interfaces where the above cannot
occur, notably where there is no way to insert an object into the collection.
Such an interface is IEnumerable<T>.
If instead you say:
IEnumerable<object> objects = strings;
There is no way we can put the wrong kind of thing into strings through objects,
because objects doesn't have a method that takes
an element in. Variance is about allowing assignments such as this in cases
where it is safe. The result is that a lot of situations that were previously
surprising now just work.
Covariance
In .NET 4.0 the IEnumerable<T>
interface will be declared in the following way:
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T>
GetEnumerator();
}
public interface IEnumerator<out T> : IEnumerator
{
bool
MoveNext();
T
Current { get; }
}
The "out" in these
declarations signifies that the T
can only occur in output position in the interface – the compiler will complain
otherwise. In return for this restriction, the interface becomes "covariant" in
T, which means that an IEnumerable<A> is considered an IEnumerable<B> if A has a reference conversion to B.
As a result, any sequence of strings is also e.g. a sequence
of objects.
This is useful e.g. in many LINQ methods. Using the
declarations above:
var result = strings.Union(objects); // succeeds with an
IEnumerable<object>
This would previously have been disallowed, and you would
have had to to some cumbersome wrapping to get the two sequences to have the
same element type.
Contravariance
Type parameters can also have an "in"
modifier, restricting them to occur only in input positions. An example is IComparer<T>:
public interface IComparer<in T>
{
public
int Compare(T left, T right);
}
The somewhat baffling result is that an IComparer<object> can in fact be
considered an IComparer<string>!
It makes sense when you think about it: If a comparer can compare any two objects,
it can certainly also compare two strings.
This property is referred to as contravariance.
A generic type can have both in
and out modifiers on its type parameters, as
is the case with the Func<…>
delegate types:
public delegate TResult Func<in TArg, out TResult>(TArg
arg);
Obviously the argument only ever comes in, and the result only ever comes out. Therefore a Func<object,string>
can in fact be used as a Func<string,object>.
Download complete features list here:
http://code.msdn.microsoft.com/csharpfuture/Release/ProjectReleases.aspx?ReleaseId=1686