Creating a Generic Method


As the preceding examples have shown, methods inside a generic class can make use of a class’s type parameter and are, therefore, automatically generic relative to the type parameter. However, it is possible to declare a generic method that uses one or more type parameters of its own. Furthermore, it is possible to create a generic method that is enclosed within a non-generic class.

Let’s begin with an example. The following program declares a non-generic class called ArrayUtils and a static generic method within that class called copyInsert( ). The copyInsert( ) method copies the contents of one array to another, inserting a new element at a specified location in the process. It can be used with any type of array.

 // Demonstrate a generic method. using System; // A class of array utilities.  Notice that this is not // a generic class. class ArrayUtils {   // Copy an array, inserting a new element   // in the process.  This is a generic method.   public static bool copyInsert<T>(T e, int idx,                                    T[] src, T[] target) {     // See if target array is big enough.     if(target.Length < src.Length+1)       return false;     // Copy src to target, inserting e at idx in the process.     for(int i=0, j=0; i < src.Length; i++, j++) {       if(i == idx) {         target[j] = e;         j++;       }       target[j] = src[i];     }     return true;   } } class GenMethDemo {   public static void Main() {     int[] nums = { 1, 2, 3 };     int[] nums2 = new int[4];     // Display contents of nums.     Console.Write("Contents of nums: ");     foreach(int x in nums)       Console.Write(x + " ");     Console.WriteLine();     // Operate on an int array.     ArrayUtils.copyInsert(99, 2, nums, nums2);     // Display contents of nums2.     Console.Write("Contents of nums2: ");     foreach(int x in nums2)       Console.Write(x + " ");     Console.WriteLine();     // Now, use copyInsert on an array of strings.     string[] strs = { "Generics", "are", "powerful."};     string[] strs2 = new string[4];     // Display contents of strs.     Console.Write("Contents of strs: ");     foreach(string s in strs)       Console.Write(s + " ");     Console.WriteLine();     // Insert into a string array.     ArrayUtils.copyInsert("in C#", 1, strs, strs2);     // Display contents of strs2.     Console.Write("Contents of strs2: ");     foreach(string s in strs2)       Console.Write(s + " ");     Console.WriteLine();     // This call is invalid because the first argument     // is of type double, and the third and fourth arguments     // have base types of int. //    ArrayUtils.copyInsert(0.01, 2, nums, nums2);   } }

The output from the program is shown here:

 Contents of nums: 1 2 3 Contents of nums2: 1 2 99 3 Contents of strs: Generics are powerful. Contents of strs2: Generics in C# are powerful.

Let’s examine copyInsert( ) closely. First, notice how it is declared by this line:

 public static bool copyInsert<T>(T e, int idx,                                  T[] src, T[] target) {

The type parameter is declared after the method name, but before the parameter list. Also notice that copyInsert( ) is static, enabling it to be called independently of any object. Understand, though, that generic methods can be either static or non-static. There is no restriction in this regard.

Now, notice how copyInsert( ) is called within Main( ) by use of the normal call syntax, without the need to specify type arguments. This is because the types of the arguments are automatically discerned, and the type of T is adjusted accordingly. This process is called type inference. For example, in the first call:

 ArrayUtils.copyInsert(99, 2, nums, nums2);

the type of T becomes int because 99 and the base types of nums and nums2 are int. In the second call, string types are used, and T is replaced by string.

Now, notice the commented-out code, shown here:

 //    ArrayUtils.copyInsert(0.01, 2, nums, nums2);

If you remove the comment symbol and then try to compile the program, you will receive an error. The reason is that the type of the first argument is double, but the base types of nums and nums2 are int. However, all three types must be substituted for the same type parameter, T. This causes a type mismatch, which results in a compile-time error. This ability to enforce type safety is one of the most important advantages of generic methods.

The syntax used to create copyInsert( ) can be generalized. Here is the general form of a generic method:

 ret-type meth-name<type-parameter-list>(param-list) { // ...

In all cases, type-parameter-list is a comma-separated list of type parameters. Notice that for a generic method, the type parameter list follows the method name.

Using Explicit Type Arguments to Call a Generic Method

Although implicit type inference is adequate for most invocations of a generic method, it is possible to explicitly specify the type argument. To do so, specify the type argument after the method name when calling the method. For example, here copyInsert( ) is explicitly passed type string:

 ArrayUtils.copyInsert<string>("in C#", 1, strs, strs2);

A common reason for explicitly specifying the type when calling a generic method occurs when class hierarchies are involved. For example, assume the following classes:

 class A {   // ... } class B: A {   // ... }

Next, assuming the version of copyInsert( ) shown in the preceding program, you might try a sequence like the following to call copyInsert( ):

 B[] b = { new B(), new B(), new B() }; A[] a = new A[4]; // Insert an A into an array of B, copying to an A array. ArrayUtils.copyInsert(new A(), 1, b, a); // Error, ambiguous!

Here, the compiler cannot infer what type should be substituted for T. Should it be A or B? Remember, it is legal for a base class reference to refer to a derived class object. Thus, it is not inherently wrong to copy the contents of b into a. However, this works only if the type substituted for T is A, the base class. Unfortunately, the compiler doesn’t know this.

To fix the situation, simply explicitly specify the type, as shown here:

 ArrayUtils.copyInsert<A>(new A(), 1, b, a); // Fixed!

Here, A is explicitly substituted for T, and the line compiles and runs without error.

Using a Constraint with a Generic Method

You can add constraints to the type arguments of a generic method by specifying them after the parameter list. For example, the following version of copyInsert( ) will work only with reference types:

 public static bool copyInsert<T>(T e, int idx,                                  T[] src, T[] target) where T : class {

If you were to try this version in the program shown earlier, then the following call to copyInsert( ) would not compile because int is a value type, not a reference type:

 // Now wrong because T must be reference type! ArrayUtils.copyInsert(99, 2, nums, nums2); // Now illegal!




C# 2.0(c) The Complete Reference
C# 2.0: The Complete Reference (Complete Reference Series)
ISBN: 0072262095
EAN: 2147483647
Year: 2006
Pages: 300

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net