Posted On Monday, July 20, 2009 at
at
8:20 AM
by test
Genericsin C# 4.0 syntax
C# 4.0 will introduce a set of changes to the language that removes the surprising behavior that closed generic types can't be treated as covariant or contravariant without introducing yet another breach in the type safety system. C# 4.0 introduces syntax to treat some generic types as safely covariant and safely contravariant.
The C# 4.0 language specification uses the terms "output safe" and "input safe" to describe type parameters that are safely covariant or contravariant, respectively. Those terms are somewhat more descriptive, if less precise, to describe how covariance and contravariance work. A couple examples will make it clearer.
The familiar IEnumerable and IEnumerator interfaces are output safe. Therefore, both interfaces can be treated as covariant. Furthermore, those methods are safely covariant. In C# 4.0, both of those interfaces have been annotated with the new "out" contextual keyword to indicate that they can be treated safely covariant:
public interface IEnumerable : IEnumerable
{
IEnumerator GetEnumerator();
}
public interface IEnumerator : IEnumerator
{
bool MoveNext();
T Current { get; }
}
Notice the addition of the "out" contextual keyword. That signifies that T is covariant. It will compile cleanly because T appears only in output positions, and is output safe. That means, beginning with C# 4.0, you can use an IEnumerable where the formal parameter list expects an IEnumerable
parameters are both reference types, and are related by some inheritance relationship.
In order to support covariance and contravariance, the .NET 4.0 BCL will have several generic interfaces and delegate types updated to be safely covariant and contravariant. As you learn more about Visual Studio 2010, take the time to learn about how those language extensions on the interfaces enable you to express designs in less code, and reuse more logic safely.
What Can You Do Now?
At this point, you may be asking how this matters. After all, VS 2010 is still a future technology, and it will be some time before it will make its way to the corporate developer.
The question is how to author your code today such that it can easily take advantage of the new covariant and contravariant additions when they become available. Knowledge of the new features will help you create code that's ready to accept the in or out contextual keywords on your interface and delegate types. It will become more important to factor those interfaces into input only and output only portions, so that your interfaces can support both covariance and contravariance, as appropriate. You should examine your generic interfaces and methods to see if the parameters and return values are input safe or output safe. That will make it easier to use them in either a covariant or contravariant manner in the near future.
A more immediate need is to be able to emulate the covariant and contravariant features using the current language elements. You can't replicate all the features, because if it already worked, there's no reason for the language teams to add these features. That said, you can get close in some usages.
There are two techniques that you can often use to mitigate the need for covariance and contravariance. You can use Cast or you can create generic methods instead of covariant and contravariant methods.
The earlier WriteItems() method could be modified as a generic method easily:
private void WriteItemsGeneric(IEnumerable
sequence)
{
foreach (T item in sequence)
Console.WriteLine(item);
}
Now, you can call WriteItems() for any sequence, including a sequence of integers. In other uses, where your methods need capabilities beyond those methods in System.Object, you'll need to add constraints on the generic method, possibly even factoring out an interface contract as part of the generic method constraints. However, there will almost always be a way to create a generic method that can be used where you want to create a covariant method, or a contravariant method. When you write the method, you should convert the method to a generic method.
When you don't have access to the core method because it's in a third-party library, you can use the Cast method in the specific cast where you need to convert between IEnumerable types for two different type parameters. Of course, this can occur only where a conversion between those types exist.
Remember that the original generic WriteItems() method was coded this way:
private void WriteItems(IEnumerable sequence)
{
foreach (var item in sequence)
Console.WriteLine(item);
}
You can call that method using a sequence of integers by applying the Cast method at the call site:
IEnumerable items = Enumerable.Range(1, 50);
WriteItems(items.Cast());
The Cast() method enumerates the input collection, converting each element, and yields the converted collection as its output. While this option will not work for other types, it will always work where you need an IEnumerable conversion for different types.
The motivation behind the addition of the generic covariance and contravariance in C# 4.0 because invariant generic types are too restrictive for most uses. There are covariant and contravariant conversions that we expect to work.