Let's begin with a simple example of a generic class. The following program defines two classes. The first is the generic class Gen, and the second is GenDemo, which uses Gen.
// A simple generic class. using System; // In the following Gen class, T is a type // parameter that will be replaced by a real // type when an object of type Gen is created. class Gen<T> { T ob; // declare an object of type T // Notice that this constructor has a parameter of type T. public Gen(T o) { ob = o; } // Return ob, which is of type T. public T getob() { return ob; } // Show type of T. public void showType() { Console.WriteLine("Type of T is " + typeof(T)); } } // Demonstrate the generic class. class GenDemo { public static void Main() { // Create a Gen reference for int. Gen<int> iOb; // Create a Gen<int> object and assign its // reference to iOb. iOb = new Gen<int>(102); // Show the type of data used by iOb. iOb.showType(); // Get the value in iOb. int v = iOb.getob(); Console.WriteLine("value: " + v); Console.WriteLine(); // Create a Gen object for strings. Gen<string> strOb = new Gen<string>("Generics add power."); // Show the type of data stored in strOb. strOb.showType(); // Get the value in strOb. string str = strOb.getob(); Console.WriteLine("value: " + str); } }
The output produced by the program is shown here:
Type of T is System.Int32 value: 102 Type of T is System.String value: Generics add power.
Let's examine this program carefully.
First, notice how Gen is declared by the following line:
class Gen<T> {
Here, T is the name of a type parameter. This name is used as a placeholder for the actual type that will be passed to Gen when an object is created. Thus, T is used within Gen whenever the type parameter is needed. Notice that T is contained within < >. This syntax can be generalized. Whenever a type parameter is being declared, it is specified within angle brackets. Because Gen uses a type parameter, Gen is a generic class.
In the declaration of Gen, there is no special significance to the name T. Any valid identifier could have been used, but T is traditional. Other commonly used type parameter names include V and E. Of course, you can also use descriptive names for type parameters, such as TValue or TKey. When using a descriptive name, it is common practice to use T as the first letter.
Next, T is used to declare a variable called ob, as shown here:
T ob; // declare an object of type T
As explained, T is a placeholder for the actual type that will be specified when a Gen object is created. Thus, ob will be a variable of the type passed to T. For example, if type string is passed to T, then in that instance, ob will be of type string.
Now consider Gen's constructor:
public Gen(T o) { ob = o; }
Notice that its parameter, o, is of type T. This means that the actual type of o is determined by the type passed to T when a Gen object is created. Also, because both the parameter o and the instance variable ob are of type T, they will both be of the same actual type when a Gen object is created.
The type parameter T can also be used to specify the return type of a method, as is the case with the getob( ) method, shown here:
public T getob() { return ob; }
Because ob is also of type T, its type is compatible with the return type specified by getob( ).
The showType( ) method displays the type of T by passing T to the typeof operator. Because a real type will be substituted for T when an object of type Gen is created, typeof will obtain type information about the actual type.
The GenDemo class demonstrates the generic Gen class. It first creates a version of Gen for type int, as shown here:
Gen<int> iOb;
Look closely at this declaration. First, notice that the type int is specified within the angle brackets after Gen. In this case, int is a type argument that is passed to Gen's type parameter, T. This creates a version of Gen in which all uses of T are replaced by int. Thus, for this declaration, ob is of type int, and the return type of getob( ) is of type int.
When you pass a type argument to a generic class, you are creating what is referred to in C# as a closed constructed type. (The term closed indicates that a type argument has been specified.) Thus, Gen<int> is a closed constructed type. In essence, a generic type, such as Gen<T>, is an abstraction. It is only after a specific version, such as Gen<int>, has been constructed that a concrete type has been created. In C# terminology, a construct such as Gen<T> is called an open constructed type, because no type argument has been specified.
The next line assigns to iOb a reference to an instance of an int version of the Gen class:
iOb = new Gen<int>(102);
Notice that when the Gen constructor is called, the type argument int is also specified. This is necessary because the type of the object (in this case iOb) to which the reference is being assigned is of type Gen<int>. Thus, the reference returned by new must also be of type Gen<int>. If it isn't, a compile-time error will result. For example, the following assignment will cause a compile-time error:
iOb = new Gen<double>(118.12); // Error!
Because iOb is of type Gen<int>, it can't be used to refer to an object of Gen<double>. This type checking is one of the main benefits of generics because it ensures type safety.
The program then displays the type of ob within iOb, which is System.Int32. This is the .NET class that corresponds to int. Next, the program obtains the value of ob by use of the following line:
int v = iOb.getob();
Because the return type of getob( ) is T, which was replaced by int when iOb was declared, the return type of getob( ) is also int. Thus, this value can be assigned to an int variable.
Next, GenDemo declares an object of type Gen<string>:
Gen<string> strOb = new Gen<string>("Generics add power.");
Because the type argument is string, string is substituted for T inside Gen. This creates a string version of Gen, as the remaining lines in the program demonstrate.
No comments:
Post a Comment