A Simple Generics Example
The following program defines two classes. The first is the generic class Gen, and the second is GenericsDemo, which uses Gen.
Example
using System; using System.IO; namespace ConsoleApplication1 { // 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 a variable 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 GenericsDemo { 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); Console.ReadLine(); } } }
Output
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 specified when a Gen 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 a variable 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 bound to T when a Gen object is instantiated. For example, if type string is specified for 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 bound 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 GenericsDemo 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 bound 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.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 variable (in this case iOb) to which the reference is being assigned is of type Gen
iOb = new Gen<double>(118.12); // Error!
Because iOb is of type Gen
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, GenericsDemo declares an object of type Gen
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.
A few terms need to be defined. When you specify a type argument such as int or string for Gen, you are creating what is referred to in C# as a closed constructed type. Thus, Gen
More generally, C# defines the concepts of an open type and a closed type. An open type is a type parameter or any generic type whose type argument is (or involves) a type parameter. Any type that is not an open type is a closed type. A constructed type is a generic type for which all type arguments have been supplied. If those type arguments are all closed types, then it is a closed constructed type. If one or more of those type arguments are open types, it is an open constructed type.