- C# without Generics
- Introducing Generic Types
- Constraints
- Generic Methods
- Generic Internals
- Summary
Generic Methods
You already learned that it is relatively simple to add a generic method to a class when the class is a generic. You did this in the generic class examples so far, and it also works for static methods. Furthermore, you can use generic classes within a generic class, as you did in earlier BinaryTree listings using the following line of code:
public Pair< BinaryTree<T> > SubItems;
Generic methods are methods that use generics even when the containing class is not a generic class or the method contains type parameters not included in the generic class type parameter list. To define generic methods, you add the type parameter syntax immediately following the method name, as shown in the MathEx.Max<T> and MathEx.Min<T> examples in Listing 11.33.
Example 11.33. Defining Generic Methods
public static class MathEx { public static T Max<T>(T first, params T[] values) where T : IComparable { T maximum = first; foreach (T item in values) { if (item.CompareTo(maximum) > 0) { maximum = item; } } return maximum; } public static T Min<T>(T first, params T[] values) where T : IComparable { T minimum = first; foreach (T item in values) { if (item.CompareTo(minimum) < 0) { minimum = item; } } return minimum; } }
You use the same syntax on a generic class when the method requires an additional type parameter not included in the class type parameter list. In this example, the method is static but C# does not require this.
Note that generic methods, like classes, can include more than one type parameter. The arity (the number of type parameters) is an additional distinguishing characteristic of a method signature.
Type Inferencing
The code used to call the Min<T> and Max<T> methods looks like that shown in Listing 11.34.
Example 11.34. Specifying the Type Parameter Explicitly
Console.WriteLine( MathEx.Max<int>(7, 490)); Console.WriteLine( MathEx.Min<string>("R.O.U.S.", "Fireswamp"));
The output to Listing 11.34 appears in Output 11.4.
Example 11.4.
490 Fireswamp
Not surprisingly, the type parameters, int and string, correspond to the actual types used in the generic method calls. However, specifying the type is redundant because the compiler can infer the type from the parameters passed to the method. To avoid redundancy, you can exclude the type parameters from the call. This is known as type inferencing, and an example appears in Listing 11.35. The output of this listing appears in Output 11.5.
Example 11.35. Inferring the Type Parameter
Console.WriteLine( MathEx.Max(7, 490)); Console.WriteLine( MathEx.Min("R.O.U.S'", "Fireswamp"));
Example 11.5.
490 Fireswamp
For type inferencing to be successful, the types must match the method signature. Calling the Max<T> method using MathEx.Max(7.0, 490), for example, causes a compile error. You can resolve the error by either casting explicitly or including the type argument. Also note that you cannot perform type inferencing purely on the return type. Parameters are required for type inferencing to be allowed.
Specifying Constraints
The generic method also allows constraints to be specified. For example, you can restrict a type parameter to implement IComparable. The constraint is specified immediately following the method header, prior to the curly braces of the method block, as shown in Listing 11.36.
Example 11.36. Specifying Constraints on Generic Methods
public class ConsoleTreeControl { // Generic method Show<T> public static void Show<T>(BinaryTree<T> tree, int indent) where T : IComparable { Console.WriteLine("\n{0}{1}", "+ --".PadLeft(5*indent, ' '), tree.Item.ToString()); if (tree.SubItems.First != null) Show(tree.SubItems.First, indent+1); if (tree.SubItems.Second != null) Show(tree.SubItems.Second, indent+1); } }
Notice that the Show<T> implementation itself does not use the IComparable interface. Recall, however, that the BinaryTree<T> class did require this (see Listing 11.37).
Example 11.37. BinaryTree<T> Requiring IComparable Type Parameters
public class BinaryTree<T> where T: System.IComparable { ... }
Because the BinaryTree<T> class requires this constraint on T, and because Show<T> uses BinaryTree<T>, Show<T> also needs to supply the constraint.