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
WriteObjects(IEnumerable
can be called using a List, or other reference type.
Similarly, some interfaces contain type parameters that are input safe. For example, IEquatable has been updated noting that it's input safe:
public interface IEquatable
{
bool Equals(T other);
}
Throughout IEquatable, T appears only in input positions. Furthermore, those input positions are never annotated with the ref or out modifier. T is therefore input safe and can be treated as contravariant. Because T only appears in input locations, a less derived type can be used in the formal parameter list where a more derived type is used as the actual parameter. An IEquatable
Other interfaces are still invariant. ICollection contains methods where T appears in input positions as well as methods where T appears in output positions:
public ICollection : IEnumerable
{
void Add(T item);
// other methods elided
IEnumerator GetEnumerator();
}
The Add() method is not output safe. The GetEnumerator() method is not input safe. Because the entire interface is neither input safe nor output safe, the ICollection interface cannot be treated as either covariant or contravariant. Therefore, ICollection will remain invariant.
There are quite a few limitations on covariance and contravariance in the C# language. Those limitations are meant to minimize potential runtime errors related to misuse of those features. The limitations can be easily remembered by a couple broad guidelines.
First off, the "in" and "out" contextual keywords can only be applied to interface and delegate generic type definitions. You can't create covariant or contravariant generic class definitions. Therefore, MyClass is illegal.
The other limitations apply when you attempt to treat a particular type parameter as covariant or contravariant. Covariance and contravariance only apply when there's a reference conversion between the two specific type parameters. As I mentioned earlier, IEnumerable can be used where IEnumerable
In practice, the reference conversion rule means that you can only treat different closed generic types as covariant or contravariant when the type 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.
hey your blog design is very nice, neat and fresh and with updated content, make people feel peace and I always like browsing your site.
- Norman