Type Constraints

In this section, we present a generic Maximum method that determines and returns the largest of its three arguments (all of the same type). The generic method in this example uses the type parameter to declare both the method's return type and its parameters. Normally, when comparing values to determine which one is greater, you would use the > operator. However, this operator is not overloaded for use with every type that is built into the FCL or that might be defined by extending those types. Generic code is restricted to performing operations that are guaranteed to work for every possible type. Thus, an expression like variable1 < variable2 is not allowed unless the compiler can ensure that the operator < is provided for every type that will ever be used in the generic code. Similarly, you cannot call a method on a generic-type variable unless the compiler can ensure that all types that will ever be used in the generic code support that method.

IComparable< E > Interface

It is possible to compare two objects of the same type if that type implements the generic interface IComparable< T > (of namespace System). A benefit of implementing interface IComparable< T > is that IComparable< T > objects can be used with the sorting and searching methods of classes in the System.Collections.Generic namespacewe discuss those methods in Chapter 27, Collections. The structures in the FCL that correspond to the simple types all implement this interface. For example, the structure for simple type double is Double and the structure for simple type int is Int32both Double and Int32 implement the IComparable interface. Types that implement IComparable< T > must declare a CompareTo method for comparing objects. For example, if we have two ints, int1 and int2, they can be compared with the expression:

int1.CompareTo( int2 )

Method CompareTo must return 0 if the objects are equal, a negative integer if int1 is less than int2 or a positive integer if int1 is greater than int2. It is the responsibility of the programmer who declares a type that implements IComparable< T > to declare method CompareTo such that it compares the contents of two objects of that type and returns the appropriate result.

Specifying Type Constraints

Even though IComparable objects can be compared, they cannot be used with generic code by default, because not all types implement interface IComparable< T >. However, we can restrict the types that can be used with a generic method or class to ensure that they meet certain requirements. This featureknown as a type constraintrestricts the type of the argument supplied to a particular type parameter. Figure 26.4 declares method Maximum (lines 2033) with a type constraint that requires each of the method's arguments to be of type IComparable< T >. This restriction is important because not all objects can be compared. However, all IComparable< T > objects are guaranteed to have a CompareTo method that can be used in method Maximum to determine the largest of its three arguments.

Figure 26.4. Generic method Maximum with a type constraint on its type parameter.

(This item is displayed on page 1375 in the print version)

1 // Fig 26.4: MaximumTest.cs 2 // Generic method maximum returns the largest of three objects. 3 using System; 4 5 class MaximumTest 6 { 7 static void Main( string[] args ) 8 { 9 Console.WriteLine( "Maximum of {0}, {1} and {2} is {3} ", 10 3, 4, 5, Maximum( 3, 4, 5 ) ); 11 Console.WriteLine( "Maximum of {0}, {1} and {2} is {3} ", 12 6.6, 8.8, 7.7, Maximum( 6.6, 8.8, 7.7 ) ); 13 Console.WriteLine( "Maximum of {0}, {1} and {2} is {3} ", 14 "pear", "apple", "orange", 15 Maximum( "pear", "apple", "orange" ) ); 16 } // end Main 17 18 // generic function determines the 19 // largest of the IComparable objects 20 static T Maximum< T >( T x, T y, T z ) where T : IComparable < T > 21 { 22 T max = x; // assume x is initially the largest 23 24 // compare y with max 25 if ( y.CompareTo( max ) > 0 ) 26 max = y; // y is the largest so far 27 28 // compare z with max 29 if ( z.CompareTo( max ) > 0 ) 30 max = z; // z is the largest 31 32 return max; // return largest object 33 } // end method Maximum 34 } // end class MaximumTest  

Maximum of 3, 4 and 5 is 5 Maximum of 6.6, 8.8 and 7.7 is 8.8 Maximum of pear, apple and orange is pear

Generic method Maximum uses type parameter T as the return type of the method (line 20), as the type of method parameters x, y and z (line 20), and as the type of local variable max (line 22). Generic method Maximum's where clause (after the parameter list in line 20) specifies the type constraint for type parameter T. In this case, the clause where T : IComparable< T > indicates that this method requires the type arguments to implement interface IComparable< T >. If no type constraint is specified, the default type constraint is object.

C# provides several kinds of type constraints. A class constraint indicates that the type argument must be an object of a specific base class or one of its subclasses. An interface constraint indicates that the type argument's class must implement a specific interface. The type constraint in line 20 is an interface constraint, because IComparable< T > is an interface. You can specify that the type argument must be a reference type or a value type by using the reference type constraint (class) or the value type constraint (struct), respectively. Finally, you can specify a constructor constraintnew()to indicate that the generic code can use operator new to create new objects of the type represented by the type parameter. If a type parameter is specified with a constructor constraint, the type argument's class must provide public a parameterless or default constructor to ensure that objects of the class can be created without passing constructor arguments; otherwise, a compilation error occurs.

It is possible to apply multiple constraints to a type parameter. To do so, simply provide a comma-separated list of constraints in the where clause. If you have a class constraint, reference type constraint or value type constraint, it must be listed firstonly one of these types of constraints can be used for each type parameter. Interface constraints (if any) are listed next. The constructor constraint is listed last (if there is one).

Analyzing the Code

Method Maximum assumes that its first argument (x) is the largest and assigns it to local variable max (line 22). Next, the if statement at lines 2526 determines whether y is greater than max. The condition invokes y's CompareTo method with the expression y.CompareTo( max ). If y is greater than max, then y is assigned to variable max (line 26). Similarly, the statement at lines 2930 determines whether z is greater than max. If so, line 30 assigns z to max. Then, line 32 returns max to the caller.

In Main (lines 716), line 10 calls Maximum with the integers 3, 4 and 5. Generic method Maximum is a match for this call, but its arguments must implement interface IComparable< T > to ensure that they can be compared. Type int is a synonym for struct Int32, which implements interface IComparable< int >. Thus, ints (and other simple types) are valid arguments to method Maximum.

Line 12 passes three double arguments to Maximum. Again, this is allowed because double is a synonym for the Double struct, which implements IComparable< double >. Line 15 passes Maximum three strings, which are also IComparable< string > objects. Note that we intentionally placed the largest value in a different position in each method call (lines 10, 12 and 15) to show that the generic method always finds the maximum value, regardless of its position in the argument list and regardless of the inferred type argument.

Категории