Theme index -- Keyboard shortcut: 'u'  Previous theme in this lecture -- Keyboard shortcut: 'p'  Next slide in this lecture -- Keyboard shortcut: 'n'Generic Types and Methods

A complete PDF version of the text book is now available. The PDF version is an almost complete subset of the HTML version (where only a few, long program listings have been removed). See here.

43.  Generic Methods

We are used to working with procedures, functions, and methods with parameters. Procedures, functions and methods are all known as abstractions. A parameter is like a variable that generalizes the abstraction. Each parameter of a procedure, a function, or a method is of a particular type. In this chapter we shall see how such types themselves can be passed as parameters to methods. When methods are parameterized with types, we talk about generic methods.

43.1 Generic Methods43.3 Generic types and methods - Pros and Cons
43.2 Generic Delegates
 

43.1.  Generic Methods
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

In Section 42.2 we realized that a generic type (such as a generic class) is a template from which it is possible to construct a real class. In the same way, a generic method is template from which we can construct a real method.

In C# and similar languages, all methods belong to classes. Some of these classes are generic, some are just simple, ordinary classes. We can have generic methods in both generic types, and in non-generic types.

Our first example in Program 43.1 is the generic method ReportCompare in the non-generic class StringApp. ReportCompare is a method in the client class of String<T> which we encountered in Section 42.4. When we first met it, we where not interested in the details of it, so therefore it was dimmed in Program 42.6.

Notice first that the method ReportCompare takes two ordinary parameters s and t. They are both of type String<T> for some given type T. The method is supposed to report the ordering of s relative to t via output written to the console. T is a (formal) type parameter of the method. Type parameters of methods are given in "triangular brackets" <...> in between the method name and the ordinary parameter list. It is highlighted with purple in Program 43.1.

The formal type parameter of ReportCompare is passed on as an actual type parameter to our generic class String<T> from Section 42.4. If we look at our definition of the generic class String<T> in Program 42.5 we notice that T must implement Icomparable<T>. This is a constraint of T, identical to one of the constraints of type parameters of types, see Section 42.3. The only way to ensure this in Program 43.1 is to add the constraint to the generic method. This is the blue part, see line 15.

Notice in line 7-13 of Program 43.1 that the actual type parameter of ReportCompare is not given explicitly. The actual type parameters of the five calls are conveniently inferred from the context. It is, however, possible to pass the actual type parameter explicitly. If we chose to do so, line 7 of Program 43.1 would be

  ReportCompare<int>(new String<int>(), new String<int>(1));

The remaining aspects of ReportMethod are simple and straightforward.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;

class StringApp{

  public static void Main(){  
                              
    ReportCompare(new String<int>(), new String<int>(1));             
    ReportCompare(new String<int>(1), new String<int>(1));            
    ReportCompare(new String<int>(1,2,3), new String<int>(1));        
    ReportCompare(new String<bool>(false), 
                  new String<bool>(false, true, false));        
    ReportCompare(new String<bool>(true, true, false), 
                  new String<bool>(true, true,false));    
  }

  public static void ReportCompare<T>(String<T> s, String<T> t)
    where T: IComparable<T>{
    Console.WriteLine("Result of comparing {0} and {1}: {2}", 
                      s, t, s.CompareTo(t));
  }   

}
Program 43.1    The generic method ReportCompare in the generic String programs.

Let us now study an additional program example with generic methods. Program 43.2 contains a bubblesort method in line 5-11. Bubblesort sorts an array of element type T, where T is a type parameter of the method. The type parameter makes our bubblesort method more general, because it allow us to sort an array of arbitrary type T. The only requirement is, quite naturally, that objects/values of type type T should be comparable, such that we can ask if one value is less than or equal to another value. This is expressed by the Icomparable<T> constraint on T at the end of line 5.

The implementation of bubblesort in Program 43.2 has no surprises. In a double for loop we compare and swap elements. Comparison is made possible because a[i] values are of type T that implements Icomparable<T>. Swapping of elements are done by the Swap method via use of C# ref parameters, see Section 20.6. Notice that Swap is also a generic method, because it can swap values/objects of arbitrary types. Be sure to notice the formal type parameter T of Swap in line 13.

Finally we have the generic method ReportArray, (see line 18-21), which simply prints the values of the array to standard output.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;

class SortDemo{

  static void BubbleSort<T>(T[] a) where T: IComparable<T>{
   int n = a.Length;
   for (int i = 0; i < n - 1; ++i)
     for (int j = n - 1; j > i; --j)
       if (a[j-1].CompareTo(a[j]) > 0)
          Swap(ref a[j-1], ref a[j]);
  }

  public static void Swap<T>(ref T a, ref T b){
    T temp;
    temp = a; a = b; b = temp;
  }

  public static void ReportArray<T>(T[] a){
    foreach(T t in a) Console.Write("{0,4}", t);
    Console.WriteLine();  
  }

  public static void Main(){
    double[] da = new double[]{5.7, 3.0, 6.9, -5,3, 0.3};

    Die[] dia = new Die[]{new Die(), new Die(),  new Die(), 
                          new Die(),  new Die(),  new Die()};

    ReportArray(da); BubbleSort(da); ReportArray(da);
    Console.WriteLine();
    ReportArray(dia); BubbleSort(dia); ReportArray(dia);
    Console.WriteLine();

    // Equivalent:
    ReportArray(da); BubbleSort<double>(da); ReportArray(da);
    Console.WriteLine();
    ReportArray(dia); BubbleSort<Die>(dia); ReportArray(dia);
  }

}
Program 43.2    A generic bubble sort program.

In the Main method we make an array of doubles and an array of dice. Values of type double are comparable. We compile the program with a version of class Die that implements IComparable<T>, such as the Die class of Program 42.13. The calls of BubbleSort in line 29 and 31 do not supply an actual type parameter to BubbleSort<T>. The compiler is smart enough to infer the actual type parameter from the declared types of the variables da and dia respectively. In line 35 and 37 we show equivalent calls of BubbleSort to which we explicitly supply the actual type parameters double and Die.

The output of Program 43.2 is shown in Listing 43.3 (only on web).

It should be noticed that the generic delegate Function<S,T> in line 6 of Program 43.4 corresponds to the predefined generic delegate called Func<S,T>.

1
2
3
4
5
6
7
8
9
10
11
 5,7   3 6,9  -5   3 0,3
  -5 0,3   3   3 5,7 6,9

 [4] [3] [1] [1] [5] [2]
 [1] [1] [2] [3] [4] [5]

  -5 0,3   3   3 5,7 6,9
  -5 0,3   3   3 5,7 6,9

 [1] [1] [2] [3] [4] [5]
 [1] [1] [2] [3] [4] [5]
Listing 43.3    Output from the generic bubble sort program.

 

43.2.  Generic Delegates
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

Delegates were introduced in Section 22.1. Recall from there that a delegate is a type of methods. In the previous section we learned about generic methods. It therefore not surprising that we also need to discuss generic delegates.

In Program 22.3 we introduced a delegate NumericFunction, which covers all function from double to double. In the same program we also introduced Compose, which composes two numeric functions to a single numeric function. In mathematical notation, the composition of f and g is denoted f o g, and it maps x to f(g(x)). We are now going to generalize the function Compose, such that it can be used on other functions of more general signatures.

Let us assume that we work with two functions f and g of the following signatures:

Thus, g maps a value of type T to a value of type U. f maps a value of type U to a value of type S. The composite function f o g therefore maps a value of type T to a value of type S via a value of type U:

In line 6 of Program 43.4 we show a delegate called Function, which is a function type that maps a value of type S to values of type T. (It corresponds to NumericFunction in Program 22.3). In line 10-13 of Program 43.4 we show the function Compose, which we motivated above. Function is a generic delegate because it is type parameterized. Compose is a generic method, as discussed in Section 43.1. The generic method PrintTableOfFunction, shown in line 16-23, takes a Function f and an array inputValues of type S[], and it applies and prints f(s) on each element s of inputValues.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System;

public class CompositionDemo {

  // A function from S to T
  public delegate T Function <S,T>(S d);

  // The generic function for function composition
  // from T to S via U
  public static Function<T,S> Compose<T,U,S>
                   (Function<U,S> f, Function<T,U> g){
    return delegate(T d){return f(g(d));};
  }   

  // A generic PrintTable function
  public static void PrintTableOfFunction<S,T>
                   (Function<S,T> f, string fname, 
                    S[] inputValues){
    foreach(S s in inputValues)
      Console.WriteLine("{0,35}({1,-4:F3}) = {2}", fname, s, f(s));

    Console.WriteLine();
  }   

  // DieFromInt: int -> Die
  public static Die DieFromInt(int i){
    return new Die(i);
  }

  // Round: double -> int
  public static int Round(double d){
    return (int)(Math.Round(d));
  }

  public static void Main(){
    double[] input = new double[25];
    for(int i = 0; i < 25; i++)
      input[i] = (double) (i*2);

    // Compose(DieFromInt, Round): double -> Die 
    // (via int) 

    PrintTableOfFunction(Compose<double,int,Die>(DieFromInt, Round), 
                         "Die of double", 
                         input);
  }

}
Program 43.4    An example that involves other types than double.

In line 43 of Main we compose the two functions DieFromInt and Round. They are both programmed explicitly, in line 26 and 31 respectively. The function Round maps a double to an int. The function DieFromInt maps an int to a Die. Thus, Compose(DieFromInt, Round) maps a double to a Die. Notice how we pass the three involved types double, int, and Die as actual type parameters to Compose in line 43.

The version of class Die used in Program 43.4 can, for instance, be the class shown in Program 12.6. The parameter of the constructor determines the maximum number of eyes of the die.

The output of Program 43.4 is shown in Listing 43.5 (only on web).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
                      Die of double(0,000) = Die[0]:1
                      Die of double(2,000) = Die[2]:2
                      Die of double(4,000) = Die[4]:4
                      Die of double(6,000) = Die[6]:6
                      Die of double(8,000) = Die[8]:5
                      Die of double(10,000) = Die[10]:8
                      Die of double(12,000) = Die[12]:2
                      Die of double(14,000) = Die[14]:4
                      Die of double(16,000) = Die[16]:10
                      Die of double(18,000) = Die[18]:9
                      Die of double(20,000) = Die[20]:20
                      Die of double(22,000) = Die[22]:16
                      Die of double(24,000) = Die[24]:9
                      Die of double(26,000) = Die[26]:18
                      Die of double(28,000) = Die[28]:10
                      Die of double(30,000) = Die[30]:16
                      Die of double(32,000) = Die[32]:15
                      Die of double(34,000) = Die[34]:27
                      Die of double(36,000) = Die[36]:17
                      Die of double(38,000) = Die[38]:10
                      Die of double(40,000) = Die[40]:13
                      Die of double(42,000) = Die[42]:24
                      Die of double(44,000) = Die[44]:3
                      Die of double(46,000) = Die[46]:21
                      Die of double(48,000) = Die[48]:37
Listing 43.5    Output from the example that involves the types double, int, and Die.

 

43.3.  Generic types and methods - Pros and Cons
Contents   Up Previous Next   Slide Annotated slide Aggregated slides    Subject index Program index Exercise index 

In this final section about generic types and methods we will briefly summarize the advantages and disadvantages of generics.

  • Advantages

    • Readability and Documentation

      • More precise indication of types.

      • Less downcasting from class Object

    • Type Checking

      • Better and more precise typechecking

    • Efficiency

      • There is a potential for more efficient programs

      • Less casting - fewer boxings

  • Disadvantages

    • Complexity

      • Yet another abstraction and parametrization-level on top of the existing

This ends the general discussion of generics. In the lecture about collections, from Chapter 44 to Chapter 48, we will make heavy use of generic types.

Generated: Monday February 7, 2011, 12:20:57
Theme index -- Keyboard shortcut: 'u'  Previous theme in this lecture -- Keyboard shortcut: 'p'  Next slide in this lecture -- Keyboard shortcut: 'n'