Essential C# 2.0

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.

Listing 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.

Listing 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.

Output 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.

Listing 11.35. Inferring the Type Parameter

Console.WriteLine( MathEx.Max(7, 490)); Console.WriteLine( MathEx.Min("R.O.U.S'", "Fireswamp"));

Output 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.

Listing 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).

Listing 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.

Advanced Topic: Casting inside a Generic Method

Sometimes you should be wary of using genericsfor instance, when using it specifically to bury a cast operation. Consider the following method, which converts a stream into an object:

public static T Deserialize<T>( Stream stream, IFormatter formatter) { return (T)formatter.Deserialize(stream); }

The formatter is responsible for removing data from the stream and converting it to an object. The Deserialize() call on the formatter returns data of type object. A call to use the generic version of Deserialize() looks something like this:

string greeting = Deserialization.Deserialize<string>(stream, formatter);

The problem with this code is that to the user of the method, Deserialize<T>() appears to be strongly typed. However, a cast operation is still performed implicitly rather than explicitly, as in the case of the nongeneric equivalent shown here:

string greeting = (string)Deserialization.Deserialize(stream, formatter);

A method using an explicit cast is more explicit about what is taking place than is a generic version with a hidden cast. Developers should use care when casting in generic methods if there are no constraints to verify cast validity.

Категории