Generic Method Implementation

If the operations performed by several overloaded methods are identical for each argument type, the overloaded methods can be more compactly and conveniently coded using a generic method. You can write a single generic method declaration that can be called at different times with arguments of different types. Based on the types of the arguments passed to the generic method, the compiler handles each method call appropriately.

Figure 26.3 reimplements the application of Fig. 26.1 using a generic PrintArray method (lines 2430). Note that the PrintArray method calls in lines 16, 18 and 20 are identical to those of Fig. 26.1, the outputs of the two applications are identical and the code in Fig. 26.3 is 17 lines shorter than the code in Fig. 26.1. As illustrated in Fig. 26.3, generics enable us to create and test our code once, then reuse that code for many different types of data. This demonstrates the expressive power of generics.

Figure 26.3. Printing array elements using generic method PrintArray.

1 // Fig. 26.3: GenericMethod.cs 2 // Using overloaded methods to print arrays of different types. 3 using System; 4 using System.Collections.Generic; 5 6 class GenericMethod 7 { 8 static void Main( string[] args ) 9 { 10 // create arrays of int, double and char 11 int[] intArray = { 1, 2, 3, 4, 5, 6 }; 12 double[] doubleArray = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7 }; 13 char[] charArray = { 'H', 'E', 'L', 'L', 'O' }; 14 15 Console.WriteLine( "Array intArray contains:" ); 16 PrintArray( intArray ); // pass an int array argument 17 Console.WriteLine( "Array doubleArray contains:" ); 18 PrintArray( doubleArray ); // pass a double array argument 19 Console.WriteLine( "Array charArray contains:" ); 20 PrintArray( charArray ); // pass a char array argument 21 } // end Main 22 23 // output array of all types 24 static void PrintArray< E >( E[] inputArray ) 25 { 26 foreach ( E element in inputArray ) 27 Console.Write( element + " " ); 28 29 Console.WriteLine( " " ); 30 } // end method PrintArray 31 } // end class GenericMethod

Array intArray contains: 1 2 3 4 5 6 Array doubleArray contains: 1.1 2.2 3.3 4.4 5.5 6.6 7.7 Array charArray contains: H E L L O

Line 24 begins method PrintArray's declaration. All generic method declarations have a type parameter list delimited by angle brackets (< E > in this example) that follows the method's name. Each type parameter list contains one or more type parameters, separated by commas. A type parameter is an identifier that is used in place of actual type names. The type parameters can be used to declare the return type, the parameter types and the local variable types in a generic method declaration; the type parameters act as placeholders for the types of the arguments passed to the generic method. A generic method's body is declared like that of any other method. Note that the type parameter names throughout the method declaration must match those declared in the type parameter list. For example, line 26 declares element in the foreach statement as type E, which matches the type parameter (E) declared in line 24. Also, a type parameter can be declared only once in the type parameter list but can appear more than once in the method's parameter list. Type parameter names need not be unique among different generic methods.

Common Programming Error 26 1

If you forget to include the type parameter list when declaring a generic method, the compiler will not recognize the type parameter names when they are encountered in the method. This results in compilation errors.

Method PrintArray's type parameter list (line 24) declares type parameter E as the placeholder for the array element type that PrintArray will output. Note that E appears in the parameter list as the array element type (line 24). The foreach statement header (line 26) also uses E as the element type. These are the same two locations where the overloaded PrintArray methods of Fig. 26.1 specified int, double or char as the array element type. The remainder of PrintArray is identical to the version presented in Fig. 26.1.

Good Programming Practice 26 1

It is recommended that type parameters be specified as individual capital letters. Typically, a type parameter that represents the type of an element in an array (or other collection) is named E for "element" or T for "type."

As in Fig. 26.1, the program of Fig. 26.3 begins by declaring and initializing six-element int array intArray (line 11), seven-element double array doubleArray (line 12) and five-element char array charArray (line 13). Then each array is output by calling PrintArray (lines 16, 18 and 20)once with argument intArray, once with argument doubleArray and once with argument charArray.

When the compiler encounters a method call such as line 16, it analyzes the set of methods (both non-generic and generic) that might match the method call looking for a method that matches the call exactly. If there are no exact matches, the compiler picks the best match. If there are no matching methods, or if there is more than one best match, the compiler generates an error. The complete details of method call resolution can be found in Section 14.5.5.1 of the Ecma C# Language Specification

www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf

or Section 20.9.7 of the Microsoft C# Language Specification 2.0

msdn.microsoft.com/vcsharp/programming/language/

In the case of line 16, the compiler determines that an exact match occurs if the type parameter E in lines 24 and 26 of method PrintArray's declaration is replaced with the type of the elements in the method call's argument intArray (i.e., int). Then, the compiler sets up a call to PrintArray with the int as the type argument for the type parameter E. This is known as the type inferencing process. The same process is repeated for the calls to method PrintArray in lines 18 and 20.

Common Programming Error 26 2

If the compiler cannot find a single non-generic or generic method declaration that is a best match for a method call, or if there are multiple best matches, a compilation error occurs.

You can also use explicit type arguments to indicate the exact type that should be used to call a generic function. For example, line 16 could be written as

PrintArray< int >( intArray ); // pass an int array argument

The preceding method call explicitly provides the type argument (int) that should be used to replace type parameter E in lines 24 and 26 of the PrintArray method's declaration.

The compiler also determines whether the operations performed on the method's type parameters can be applied to elements of the type stored in the array argument. The only operation performed on the array elements in this example is to output the string representation of the elements. Line 27 performs an implicit conversion for every value type array element and an implicit ToString call on every reference type array element. Since all objects have a ToString method, the compiler is satisfied that line 27 performs a valid operation for any array element.

By declaring PrintArray as a generic method in Fig. 26.3, we eliminated the need for the overloaded methods of Fig. 26.1, saving 17 lines of code and creating a reusable method that can output the string representations of the elements in any array, not just arrays of int, double or char elements.

Категории