Generics and Interface-Based Programming - Generic Interfaces as Operators
(Page 3 of 4 )
C# 2.0 has an additional interesting use for generic interfaces. In C# 2.0, it is impossible to use operators such as + or +=on generic type parameters. For example, the following code does not compile because C# 2.0 does not have operator constraints:
public class Calculator<T>
{
public T Add(T argument1,T argument2)
{
return argument1 + argument2;//Does not compile
}
//Rest of the methods
}
The compiler does not know whether the type parameter the client will specify supports the+ operator, so it will refuse to compile that code.
Nonetheless, you can compensate by using interfaces that define generic operations. Since an interface method cannot have any code in it, you can specify the generic operations at the interface level and provide a concrete type and implementation at the subclass level:
public interface ICalculator<T>
{
T Add(T argument1,T argument2);
T Subtract(T argument1,T argument2);
T Divide(T argument1,T argument2);
T Multiply(T argument1,T argument2);
}
public class Calculator : ICalculator<int>
{
public int Add(int argument1, int argument2)
{
return argument1 + argument2;
}
//Rest of the methods
}
Interface-Level Constraints
An interface can define constraints for the generic types it uses. For example:
public interface IList<T> where T : IComparable<T>
{...}
However, you should be very mindful about the implications of defining constraints at the scope of an interface. An interface should not have any shred of implementation details, to reinforce the notion of separation of interface from implementation. There are many ways to implement the generic interface, and the specific type parameters used are, after all, an implementation detail. Constraining them commonly couples the interface to specific implementation options.
For example, constraining a type parameter on an interface to derive from a particular class, like this, is a bad idea:
public class Customer
{...}
public interface IList<T> where T : Customer
{...}
This, in effect, makes the genericIList<T>useful only for managing lists of customers. If you want this level of strong typing with polymorphism, define a new interface dedicated to managing customers, instead of skewing the general-purpose definition of the genericIList<T>:
public interface ICustomerList : IList<Customer>
{...}
Constraining a default constructor, as follows, is also something to avoid:
public interface IList<T> where T : new()
{...}
Not all types have default public constructors, and the interface doesn’t really care that they do not; the interface cannot contain any implementation code that uses such constructors anyway.
Even constraining an interface’s generic type parameter to derive from another interface should be viewed with extreme caution. For example, you may think you should add a constraint forIComparable<T>to the list interface definition if you add a method that removes a specified item from the list:
public interface IList<T> where T : IComparable<T>
{
void Remove(T item);
//Rest of the interface
}
While implementing this method will often involve comparing the specified item to items in the list, which will in turn necessitate having the type parameter supportIComparable<T>, this is still an implementation detail. Perhaps the implementing data structure has other ways of comparing the type parameters it uses, or perhaps it does not implement the method at all. All of that is irrelevant to the pure interface definition. Let the class implementing the generic interface add the constraint, and keep the interface itself constraint-free:
public class List<T> : IList<T> where T : IComparable<T>
{
public void Remove(T item)
{...}
//Rest of the implementation
}
Next: Generic Derivation Constraints >>
More .NET Articles
More By O'Reilly Media
|
This article is excerpted from chapter three of Programming .NET Components, Second Edition, written by Juval Lowy (O'Reilly, 2006; ISBN: 0596007620). Check it out today at your favorite bookstore. Buy this book now.
|
|