C#’s and the CLR’s support of generic interfaces offers many great features for developers. In this sec-
tion, I’d like to discuss the benefits offered when using generic interfaces.
First, generic interfaces offer great compile-time type safety. Some interfaces (such as the non- generic IComparable interface) define methods that have Object parameters or return types. When code calls these interface methods, a reference to an instance of any type can be passed. But this is usually not desired. The following code demonstrates.
private void SomeMethod1() { Int32 x = 1, y = 2; IComparable c = x;
// CompareTo expects an Object; passing y (an Int32) is OK c.CompareTo(y); // y is boxed here
// CompareTo expects an Object; passing "2" (a String) compiles
// but an ArgumentException is thrown at runtime c.CompareTo("2");
}
Obviously, it is preferable to have the interface method strongly typed, and this is why the FCL includes a generic IComparable<in T> interface.
Here is the new version of the code revised by using the generic interface.
private void SomeMethod2() { Int32 x = 1, y = 2; IComparable<Int32> c = x;
// CompareTo expects an Int32; passing y (an Int32) is OK c.CompareTo(y); // y is not boxed here
// CompareTo expects an Int32; passing "2" (a String) results
// in a compiler error indicating that String cannot be cast to an Int32 c.CompareTo("2"); // Error
}
The second benefit of generic interfaces is that much less boxing will occur when working with val- ue types. Notice in SomeMethod1 that the non-generic IComparable interface’s CompareTo method expects an Object; passing y (an Int32 value type) causes the value in y to be boxed. However, in SomeMethod2, the generic IComparable<in T> interface’s CompareTo method expects an Int32; passing y causes it to be passed by value, and no boxing is necessary.
NoteThe FCL defines non-generic and generic versions of the IComparable, ICollection, IList, and IDictionary interfaces, as well as some others. If you are defining a type, and
you want to implement any of these interfaces, you should typically implement the generic versions of these interfaces. The non-generic versions are in the FCL for backward compat- ibility to work with code written before the .NET Framework supported generics. The non- generic versions also provide users a way of manipulating the data in a more general, less type-safe fashion.
Some of the generic interfaces inherit the non-generic versions, so your class will have to implement both the generic and non-generic versions of the interfaces. For example, the generic IEnumerable<out T> interface inherits the non-generic IEnumerable inter- face. So if your class implements IEnumerable<out T>, your class must also implement IEnumerable.
Sometimes when integrating with other code, you may have to implement a non-generic interface because a generic version of the interface simply doesn’t exist. In this case, if any of the interface’s methods take or return Object, you will lose compile-time type safety, and you will get boxing with value types. You can alleviate this situation to some extent by using a technique I describe in the “Improving Compile-Time Type Safety with Explicit Interface Method Implementations” section near the end of this chapter.
The third benefit of generic interfaces is that a class can implement the same interface multiple
times as long as different type parameters are used.
The following code shows an example of how useful this could be.
using System;
// This class implements the generic IComparable<T> interface twice public sealed class Number: IComparable<Int32>, IComparable<String> {
private Int32 m_val = 5;
// This method implements IComparable<Int32>'s CompareTo public Int32 CompareTo(Int32 n) {
return m_val.CompareTo(n);
}
// This method implements IComparable<String>'s CompareTo public Int32 CompareTo(String s) {
return m_val.CompareTo(Int32.Parse(s));
}
}
public static class Program { public static void Main() {
Number n = new Number();
// Here, I compare the value in n with an Int32 (5) IComparable<Int32> cInt32 = n;
Int32 result = cInt32.CompareTo(5);
// Here, I compare the value in n with a String ("5") IComparable<String> cString = n;
result = cString.CompareTo("5");
}
}
An interface’s generic type parameters can also be marked as contra-variant and covariant, which allows even more flexibility for using generic interfaces. For more about contra-variance and covari- ance, see the “Delegate and Interface Contra-variant and Covariant Generic Type Arguments” section in Chapter 12.