20.3 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 20.3 reimplements the app of Fig. 20.1 using a generic DisplayArray method (lines 24–32). Note that the DisplayArray method calls in lines 15, 17 and 19 are identical to those of Fig. 20.1, the outputs of the two apps are identical and the code in Fig. 20.3 is 22 lines shorter than that in Fig. 20.1. As illustrated in Fig. 20.3, generics enable us to create and test our code once, then reuse it for many different types of data. This effectively demonstrates the expressive power of generics.

Fig. 20.3 Using a generic method to display arrays of different types.

Alternate View
  1   // Fig. 20.3: GenericMethod.cs
  2   // Using a generic method to display arrays of different types.
  3   using System;
  4
  5   class GenericMethod
  6   {
  7      static void Main()
  8      {
  9         // create arrays of int, double and char
 10         int[] intArray = {1, 2, 3, 4, 5, 6};
 11         double[] doubleArray = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7};
 12         char[] charArray = {'H', 'E', 'L', 'L', 'O'};
 13
 14         Console.Write("Array intArray contains: ");
 15         DisplayArray(intArray); // pass an int array argument
 16         Console.Write("Array doubleArray contains: ");
 17         DisplayArray(doubleArray); // pass a double array argument
 18         Console.Write("Array charArray contains: ");
 19         DisplayArray(charArray); // pass a char array argument
 20      }
 21
 22      // output array of all types
 23      private static void DisplayArray<T>(T[] inputArray)
 24      {
 25         foreach (var element in inputArray)
 26         {
 27             Console.Write($"{element} ");
 28         }
 29
 30         Console.WriteLine();
 31      }
 32   }
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 23 begins method DisplayArray’s declaration, which is static so that Main can call DisplayArray. All generic method declarations have a type-parameter list delimited by angle brackets (<T> in this example) that follows the method’s name. Each type-parameter list contains one or more type parameters, separated by commas (e.g., Dictionary<K, V>). A type parameter is an identifier that’s used in place of actual type names. The type parameters can be used to declare the return type, the parameter types and local variable types in a generic method declaration; the type parameters act as placeholders for type arguments that represent the types of data that will be passed to the generic method.

The type-parameter names throughout the method declaration (if any) must match those declared in the type-parameter list. 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 separate generic methods.

Common Programming Error 20.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’re encountered in the method. This results in compilation errors.

Method DisplayArray’s type-parameter list (line 23) declares type parameter T as the placeholder for the array-element type that DisplayArray will output. Note that T appears in the parameter list as the array-element type (line 23). This is the same location where the overloaded DisplayArray methods of Fig. 20.1 specified int, double or char as the element type. The remainder of DisplayArray is identical to the version presented in Fig. 20.1. In this example though, the foreach statement infers element’s type from the array type passed to the method.

As in Fig. 20.1, the program of Fig. 20.3 begins by declaring and initializing six-element int array intArray (line 10), seven-element double array doubleArray (line 11) and five-element char array charArray (line 12). Then each array is output by calling DisplayArray (lines 15, 17 and 19)—once with argument intArray, once with argument doubleArray and once with argument charArray.

When the compiler encounters a method call such as line 15, it analyzes the set of methods (both nongeneric and generic) that might match the method call, looking for a method that best matches the call. If there’s no matching method, or if there’s more than one best match, the compiler generates an error.

In the case of line 15, the compiler determines that the best match occurs if the type parameter T in line 23 of method DisplayArray’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 DisplayArray with int as the type argument for the type parameter T. This is known as the type-inferencing process. The same process is repeated for the calls to method DisplayArray in lines 17 and 19.

Common Programming Error 20.2

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

For each variable declared with a type parameter, the compiler checks whether the operations performed on the variable are allowed for all types that the type parameter can assume. By default, a type parameter can assume any type, but we’ll show in Section 20.4 that you can restrict this to specific types. The only operation performed on each array element in this example is to output its string representation. Line 27 performs an implicit ToString call on the current 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 DisplayArray as a generic method in Fig. 20.3, we eliminated the need for the overloaded methods of Fig. 20.1, saving 22 lines of code and creating a reusable method that can output the string representations of the elements in any one-dimensional array, not just arrays of int, double or char elements.

Value Types vs. Reference Types in Generics

The compiler handles value and reference types differently in generic method calls. When a value-type argument is used for a given type parameter, the compiler generates a version of the method that’s specific to the value type—if one has been generated previously, the compiler reuses that one. So in Fig. 20.3, the compiler generates three versions of method DisplayArray—one each for types int, double and char. If DisplayArray were called with a reference type, the compiler would also generate a single version of the method that’s used by all reference types.

Explicit Type Arguments

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

DisplayArray<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 T in line 23. Though not required here, an explicit type argument would be required if the compiler cannot infer the type from the method’s argument(s).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset