CHAPTER 18
Generics

This chapter examines one of C#’s most sophisticated and powerful features: generics. Interestingly, although generics are now an indispensable part of C# programming, they were not included in the original 1.0 release. Instead, they were added by C# 2.0. It is not an overstatement to say that the addition of generics fundamentally changed the character of C#. Not only did it add a new syntactic element, it also added new capabilities and resulted in many changes and upgrades to the library. Although it has been a few years since the inclusion of generics in C#, the effects still reverberate throughout the language.

The generics feature is so important because it enables the creation of classes, structures, interfaces, methods, and delegates that work in a type-safe manner with various kinds of data. As you may know, many algorithms are logically the same no matter what type of data they are being applied to. For example, the mechanism that supports a queue is the same whether the queue is storing items of type int, string, object, or a user-defined class. Prior to generics, you might have created several different versions of the same algorithm to handle different types of data. Through the use of generics, you can define a solution once, independently of any specific type of data, and then apply that solution to a wide variety of data types without any additional effort.

This chapter describes the syntax, theory, and use of generics. It also shows how generics provide type safety for some previously difficult cases. Once you have completed this chapter, you will want to examine Chapter 25, which covers Collections. There you will find many examples of generics at work in the generic collection classes.

What Are Generics?

At its core, the term generics means parameterized types. Parameterized types are important because they enable you to create classes, structures, interfaces, methods, and delegates in which the type of data upon which they operate is specified as a parameter. Using generics, it is possible to create a single class, for example, that automatically works with different types of data. A class, structure, interface, method, or delegate that operates on a parameterized type is called generic, as in generic class or generic method.

It is important to understand that C# has always given you the ability to create generalized code by operating through references of type object. Because object is the base class of all other classes, an object reference can refer to any type of object. Thus, in pre-generics code, generalized code used object references to operate on a variety of different kinds of objects.

The problem was that it could not do so with type safety because casts were needed to convert between the object type and the actual type of the data. This was a potential source of errors because it was possible to accidentally use an incorrect cast. Generics avoid this problem by providing the type safety that was lacking. Generics also streamline the process because it is no longer necessary to employ casts to translate between object and the type of data that is actually being operated upon. Thus, generics expand your ability to re-use code, and let you do so safely and easily.


NOTE A Warning to C++ and Java Programmers: Although C# generics are similar to templates in C++ and generics in Java, they are not the same as either. In fact, there are some fundamental differences among these three approaches to generics. If you have a background in C++ or Java, it is important to not jump to conclusions about how generics work in C#.

A Simple Generics Example

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 GenericsDemo, 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 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);
  }
}

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 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<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 structure 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, GenericsDemo 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.

Before moving on, 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<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 the type parameter T (rather than an actual type, such as int) is specified.

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.

Generic Types Differ Based on Their Type Arguments

A key point to understand about generic types is that a reference of one specific version of a generic type is not type-compatible with another version of the same generic type. For example, assuming the program just shown, the following line of code is in error and will not compile:

iOb = strOb; // Wrong!

Even though both iOb and strOb are of type Gen<T>, they are references to different types because their type arguments differ.

How Generics Improve Type Safety

At this point, you might be asking yourself the following question. Given that the same functionality found in the generic Gen class can be achieved without generics, by simply specifying object as the data type and employing the proper casts, what is the benefit of making Gen generic? The answer is that generics automatically ensure the type safety of all operations involving Gen. In the process, generics eliminate the need for you to use casts and type-check code by hand.

To understand the benefits of generics, first consider the following program that creates a non-generic equivalent of Gen:

// NonGen is functionally equivalent to Gen but does not use generics.

using System;

class NonGen {
  object ob; // ob is now of type object

  // Pass the constructor a reference of type object.
  public NonGen(object o) {
    ob = o;
  }

  // Return type object.
  public object GetOb() {
    return ob;
  }

  // Show type of ob.
  public void ShowType() {
    Console.WriteLine("Type of ob is " + ob.GetType());
  }
}

// Demonstrate the non-generic class.
class NonGenDemo {
  static void Main() {
    NonGen iOb;

    // Create NonGen object.
    iOb = new NonGen(102);

    // Show the type of data stored in iOb.
    iOb.ShowType();

    // Get the value in iOb.
    // This time, a cast is necessary.
    int v = (int) iOb.GetOb();
    Console.WriteLine("value: " + v);

    Console.WriteLine();

    // Create another NonGen object and store a string in it.
    NonGen strOb = new NonGen("Non-Generics Test");

    // Show the type of data stored in strOb.
    strOb.ShowType();

    // Get the value of strOb.
    // Again, notice that a cast is necessary.
    String str = (string) strOb.GetOb();
    Console.WriteLine("value: " + str);

    // This compiles, but is conceptually wrong!
    iOb = strOb;

    // The following line results in a runtime exception.
    // v = (int) iOb.GetOb(); // runtime error!
  }
}

This program produces the following output:

Type of ob is System.Int32
value: 102
Type of ob is System.String
value: Non-Generics Test

As you can see, the output is similar to the previous version of the program.

There are several things of interest in this version. First, notice that NonGen replaces all uses of T with object. This makes NonGen able to store any type of object, as can the generic version. However, this approach is bad for two reasons. First, explicit casts must be employed to retrieve the stored data. Second, many kinds of type mismatch errors cannot be found until runtime. Let’s look closely at each problem.

We will begin with this line:

int v = (int) iOb.GetOb();

Because the return type of GetOb( ) is now object, the cast to int is necessary to enable the value returned by GetOb( ) to be unboxed and stored in v. If you remove the cast, the program will not compile. In the generic version of the program, this cast was not needed because int was specified as a type argument when iOb was constructed. In the non-generic version, the cast must be employed. This is not only an inconvenience, but a potential source of error.

Now, consider the following sequence from near the end of the program:

// This compiles, but is conceptually wrong!
iOb = strOb;

// The following line results in a runtime exception.
// v = (int) iOb.GetOb(); // runtime error!

Here, strOb is assigned to iOb. However, strOb refers to an object that contains a string, not an integer. This assignment is syntactically valid because all NonGen references are the same, and any NonGen reference can refer to any other NonGen object. However, the statement is semantically wrong, as the commented-out line shows. In that line, the return type of GetOb( ) is cast to int and then an attempt is made to assign this value to v. The trouble is that iOb now refers to an object that stores a string, not an int. Unfortunately, without generics, the compiler won’t catch this error. Instead, a runtime exception will occur when the cast to int is attempted. To see this for yourself, try removing the comment symbol from the start of the line and then compiling and running the program. A runtime error will occur.

The preceding sequence can’t occur when generics are used. If this sequence were attempted in the generic version of the program, the compiler would catch it and report an error, thus preventing a serious bug that results in a runtime exception. The ability to create type-safe code in which type-mismatch errors are caught at compile time is a key advantage of generics. Although using object references to create “generic” code has always been possible in C#, that code was not type-safe and its misuse could result in runtime exceptions. Generics prevent this from occurring. In essence, through generics, what were once runtime errors have become compile-time errors. This is a major benefit.

There is one other point of interest in the NonGen program. Notice how the type of the NonGen instance variable ob is obtained by ShowType( ):

Console.WriteLine("Type of ob is " + ob.GetType());

Recall from Chapter 11 that object defines several methods that are available to all data types. One of these methods is GetType( ), which returns a Type object that describes the type of the invoking object at runtime. Thus, even though the type of ob is specified as object in the program’s source code, at runtime, the actual type of object being referred to is known. This is why the CLR will generate an exception if you try an invalid cast during program execution.

A Generic Class with Two Type Parameters

You can declare more than one type parameter in a generic type. To specify two or more type parameters, simply use a comma-separated list. For example, the following TwoGen class is a variation of the Gen class that has two type parameters:

// A simple generic class with two type parameters: T and V.

using System;

class TwoGen<T, V> {
  T ob1;
  V ob2;

  // Notice that this constructor has parameters of type T and V.
  public TwoGen(T o1, V o2) {
    ob1 = o1;
    ob2 = o2;
  }

  // Show types of T and V.
  public void showTypes() {
     Console.WriteLine("Type of T is " + typeof(T));
     Console.WriteLine("Type of V is " + typeof(V));
  }

  public T getob1() {
    return ob1;
  }

  public V GetObj2() {
    return ob2;
  }
}

// Demonstrate two generic type parameters.
class SimpGen {
  static void Main() {

    TwoGen<int, string> tgObj =
      new TwoGen<int, string>(119, "Alpha Beta Gamma");

    // Show the types.
    tgObj.showTypes();

    // Obtain and show values.
    int v = tgObj.getob1();
    Console.WriteLine("value: " + v);
    string str = tgObj.GetObj2();
    Console.WriteLine("value: " + str);
  }
}

The output from this program is shown here:

Type of T is System.Int32
Type of V is System.String
value: 119
value: Alpha Beta Gamma

Notice how TwoGen is declared:

class TwoGen<T, V> {

It specifies two type parameters: T and V, separated by a comma. Because it has two type parameters, two type arguments must be specified when a TwoGen object is created, as shown here:

TwoGen<int, string> tgObj =
  new TwoGen<int, string>(119, "Alpha Beta Gamma");

In this case, int is substituted for T and string is substituted for V.

Although the two type arguments differ in this example, it is possible for both types to be the same. For example, the following line of code is valid:

TwoGen<string, string> x = new TwoGen<string, string>("Hello", "Goodbye");

In this case, both T and V would be of type string. Of course, if the type arguments were always the same, then two type parameters would be unnecessary.

The General Form of a Generic Class

The generics syntax shown in the preceding examples can be generalized. Here is the syntax for declaring a generic class:

class class-name<type-param-list> { // ...

Here is the syntax for declaring a reference to a generics class:

class-name<type-arg-list> var-name =

new class-name<type-arg-list>(cons-arg-list);

Constrained Types

In the preceding examples, the type parameters could be replaced by any type. For example, given this declaration

class Gen<T> {

any type can be specified for T. Thus, it is legal to create Gen objects in which T is replaced by int, double, string, FileStream, or any other type. Although having no restrictions on the type argument is fine for many purposes, sometimes it is useful to limit the types that can be used as a type argument. For example, you might want to create a method that operates on the contents of a stream, including a FileStream or MemoryStream. This situation seems perfect for generics, but you need some way to ensure that only stream types are used as type arguments. You don’t want to allow a type argument of int, for example. You also need some way to tell the compiler that the methods defined by a stream will be available for use. For example, your generic code needs some way to know that it can call the Read( ) method.

To handle such situations, C# provides constrained types. When specifying a type parameter, you can specify a constraint that the type parameter must satisfy. This is accomplished through the use of a where clause when specifying the type parameter, as shown here:

class class-name<type-param> where type-param: constraints { // ...

Here, constraints is a comma-separated list of constraints.

C# defines the following types of constraints.

• You can require that a certain base class be present in a type argument by using a base class constraint. This constraint is specified by naming the desired base class. There is a variation of this constraint, called a naked type constraint, in which a type parameter (rather than an actual type) specifies the base class. This enables you to establish a relationship between two type parameters.

• You can require that one or more interfaces be implemented by a type argument by using an interface constraint. This constraint is specified by naming the desired interface.

• You can require that the type argument supply a parameterless constructor. This is called a constructor constraint. It is specified by new( ).

• You can specify that a type argument be a reference type by specifying the reference type constraint: class.

• You can specify that the type argument be a value type by specifying the value type constraint: struct.

Of these constraints, the base class constraint and the interface constraint are probably the most often used, but all are important. Each constraint is examined in the following sections.

Using a Base Class Constraint

The base class constraint enables you to specify a base class that a type argument must inherit. A base class constraint serves two important purposes. First, it lets you use the members of the base class specified by the constraint within the generic class. For example, you can call a method or use a property of the base class. Without a base class constraint, the compiler has no way to know what type of members a type argument might have. By supplying a base class constraint, you are letting the compiler know that all type arguments will have the members defined by that base class.

The second purpose of a base class constraint is to ensure that only type arguments that support the specified base class are used. This means that for any given base class constraint, the type argument must be either the base class, itself, or a class derived from that base class. If you attempt to use a type argument that does not match or inherit the specified base class, a compile-time error will result.

The base class constraint uses this form of the where clause:

where T: base-class-name

Here, T is the name of the type parameter, and base-class-name is the name of the base class. Only one base class can be specified.

Here is a simple example that demonstrates the base class constraint mechanism:

// A simple demonstration of a base class constraint.

using System;

class A {
  public void Hello() {
    Console.WriteLine("Hello");
  }
}

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

// Class C does not inherit A.
class C { }

// Because of the base class constraint, all type arguments
// passed to Test must have A as a base class.
class Test<T> where T : A {
  T obj;

  public Test(T o) {
    obj = o;
  }

  public void SayHello() {
    // OK to call Hello() because it’s declared
    // by the base class A.
    obj.Hello();
  }
}

class BaseClassConstraintDemo {
  static void Main() {
     A a = new A();
     B b = new B();
     C c = new C();

     // The following is valid because A is the specified base class.
     Test<A> t1 = new Test<A>(a);

     t1.SayHello();

     // The following is valid because B inherits A.
     Test<B> t2 = new Test<B>(b);

     t2.SayHello();

     // The following is invalid because C does not inherit A.
//     Test<C> t3 = new Test<C>(c); // Error!
//     t3.SayHello(); // Error!
  }
}

In this program, class A is inherited by B, but not by C. Notice also that A declares a method called Hello( ). Next, notice that Test is a generic class that is declared like this:

class Test<T> where T : A {

The where clause stipulates that any type argument specified for T must have A as a base class.

Now notice that Test declares the method SayHello( ), shown next:

public void SayHello() {
  // OK to call Hello() because it’s declared
  // by the base class A.
  obj.Hello(); }

This method calls Hello( ) on obj, which is a T object. The key point is that the only reason that Hello( ) can be called is because the base class constraint requires that any type argument bound to T must be A or inherit A, and A declares Hello( ). Thus, any valid T will define Hello( ). If the base class constraint had not been used, the compiler would have no way of knowing that a method called Hello( ) could be called on an object of type T. You can prove this for yourself by removing the where clause. The program will no longer compile because the Hello( ) method will be unknown.

In addition to enabling access to members of the base class, the base class constraint enforces that only types that inherit the base class can be passed as type arguments. This is why the following two lines are commented-out:

//    Test<C> t3 = new Test<C>(c); // Error!
//    t3.SayHello(); // Error!

Because C does not inherit A, it can’t be used as a type argument when constructing a Test object. You can prove this by removing the comment symbols and trying to recompile.

Before continuing, let’s review the two effects of a base class constraint: A base class constraint enables a generic class to access the members of the base class. It also ensures that only those type arguments that fulfill this constraint are valid, thus preserving type safety.

Although the preceding example shows the “how” of base class constraints, it does not show the “why.” To better understand the value of base type constraints, let’s work through another, more practical example. Assume you want to create a mechanism that manages lists of telephone numbers. Furthermore, assume you want to use different lists for different groupings of numbers. Specifically, you want one list for friends, another for suppliers, and so on. To accomplish this, you might start by creating a base class called PhoneNumber that stores a name and a phone number linked to that name. Such a class might look like this:

// A base class that stores a name and phone number.
class PhoneNumber {
  public PhoneNumber(string n, string num) {
    Name = n;
    Number = num;
  }

  // Auto-implemented properties that hold a name and phone number.
  public string Number { get; set; }
  public string Name { get; set; }
}

Next, you create two classes that inherit PhoneNumber: Friend and Supplier. They are shown here:

// A class of phone numbers for friends.
class Friend : PhoneNumber {

  public Friend(string n, string num, bool wk) :
    base(n, num)
  {

    IsWorkNumber = wk;
  }

  public bool IsWorkNumber { get; private set; }
  // ...
}

// A class of phone numbers for suppliers.
class Supplier : PhoneNumber {
  public Supplier(string n, string num) :
    base(n, num) { }

  // ...
}

Notice that Friend adds a property called IsWorkNumber, which returns true if the telephone number is a work number.

To manage telephone lists, you create a class called PhoneList. Because you want this class to manage any type of phone list, you make it generic. Furthermore, because part of the list management is looking up numbers given names, and vice versa, you add the constraint that requires that the type of objects stored in the list must be instances of a class derived from PhoneNumber.

// PhoneList can manage any type of phone list
// as long as it is derived from PhoneNumber.
class PhoneList<T> where T : PhoneNumber {
  T[] phList;
  int end;

  public PhoneList() {
    phList = new T[10];
    end = 0;
  }

  // Add an entry to the list.
  public bool Add(T newEntry) {
    if(end == 10) return false;

    phList[end] = newEntry;
    end++;

    return true;
  }

  // Given a name, find and return the phone info.
  public T FindByName(string name) {

    for(int i=0; i<end; i++) {
      // Name can be used because it is a member of
      // PhoneNumber, which is the base class constraint.
      if(phList[i].Name == name)
         return phList[i];
     }

     // Name not in list.
     throw new NotFoundException();
  }

  // Given a number, find and return the phone info.
  public T FindByNumber(string number) {

    for(int i=0; i<end; i++) {
      // Number can be used because it is also a member of
      // PhoneNumber, which is the base class constraint.
      if(phList[i].Number == number)
         return phList[i];
     }

     // Number not in list.
     throw new NotFoundException();
  }

  // ...
}

The base class constraint enables code inside PhoneList to access the properties Name and Number for any type of telephone list. It also guarantees that only valid types are used to construct a PhoneList object. Notice that PhoneList throws a NotFoundException if a name or number is not found. This is a custom exception that is declared as shown here:

class NotFoundException : Exception {
  /* Implement all of the Exception constructors. Notice that
     the constructors simply execute the base class constructor.
     Because NotFoundException adds nothing to Exception,
     there is no need for any further actions. */
  public NotFoundException() : base() { }
  public NotFoundException(string message) : base(message) { }
  public NotFoundException(string message, Exception innerException) :
    base(message, innerException) { }
  protected NotFoundException(
    System.Runtime.Serialization.SerializationInfo info,
    System.Runtime.Serialization.StreamingContext context) :
      base(info, context) { }
}

Although only the default constructor is used by this example, NotFoundException implements all of the constructors defined by Exception for the sake of illustration. Notice that these constructors simply invoke the equivalent base class constructor defined by Exception. Because NotFoundException adds nothing to Exception, there is no reason for any further action.

The following program puts together all the pieces and demonstrates PhoneList. Notice that a class called EmailFriend is also created. This class does not inherit PhoneNumber. Thus, it cannot be used to create a PhoneList.

// A more practical demonstration of a base class constraint.

using System;

// A custom exception that is thrown if a name or number is not found.
class NotFoundException : Exception {
  /* Implement all of the Exception constructors. Notice that
     the constructors simply execute the base class constructor.
     Because NotFoundException adds nothing to Exception,
     there is no need for any further actions. */
  public NotFoundException() : base() { }
  public NotFoundException(string message) : base(message) { }
  public NotFoundException(string message, Exception innerException) :
    base(message, innerException) { }
  protected NotFoundException(
    System.Runtime.Serialization.SerializationInfo info,
    System.Runtime.Serialization.StreamingContext context) :
      base(info, context) { }
}
// A base class that stores a name and phone number.
class PhoneNumber {

  public PhoneNumber(string n, string num) {
    Name = n;
    Number = num;
  }

  public string Number { get; set; }
  public string Name { get; set; }
}

// A class of phone numbers for friends.
class Friend : PhoneNumber {
  public Friend(string n, string num, bool wk) :
     base(n, num)
  {
    IsWorkNumber = wk;
  }

  public bool IsWorkNumber { get; private set; }

  // ...
}

// A class of phone numbers for suppliers.
class Supplier : PhoneNumber {
  public Supplier(string n, string num) :
    base(n, num) { }

  // ...
}

// Notice that this class does not inherit PhoneNumber.
class EmailFriend {
  // ...
}

// PhoneList can manage any type of phone list
// as long as it is derived from PhoneNumber.
class PhoneList<T> where T : PhoneNumber {
  T[] phList;
  int end;

  public PhoneList() {
    phList = new T[10];
    end = 0;
  }

  // Add an entry to the list.
  public bool Add(T newEntry) {
    if(end == 10) return false;

    phList[end] = newEntry;
    end++;

     return true;
  }

  // Given a name, find and return the phone info.
  public T FindByName(string name) {

    for(int i=0; i<end; i++) {
      // Name can be used because it is a member of
      // PhoneNumber, which is the base class constraint.
      if(phList[i].Name == name)
        return phList[i];
    }
     // Name not in list.
     throw new NotFoundException();
  }

  // Given a number, find and return the phone info.
  public T FindByNumber(string number) {

    for(int i=0; i<end; i++) {
      // Number can be used because it is also a member of
      // PhoneNumber, which is the base class constraint.
      if(phList[i].Number == number)
        return phList[i];
     }

    // Number not in list.
     throw new NotFoundException();
  }

  // ...
}

// Demonstrate base class constraints.
class UseBaseClassConstraint {
  static void Main() {

    // The following code is OK because Friend
    // inherits PhoneNumber.
    PhoneList<Friend> plist = new PhoneList<Friend>();
    plist.Add(new Friend("Tom", "555-1234", true));
    plist.Add(new Friend("Gary", "555-6756", true));
     plist.Add(new Friend("Matt", "555-9254", false));

    try {
      // Find the number of a friend given a name.
      Friend frnd = plist.FindByName("Gary");

      Console.Write(frnd.Name + ": " + frnd.Number);

      if(frnd.IsWorkNumber)
        Console.WriteLine(" (work)");
      else
         Console.WriteLine();
    } catch(NotFoundException) {
       Console.WriteLine("Not Found");
     }

     Console.WriteLine();

    // The following code is also OK because Supplier
    // inherits PhoneNumber.
    PhoneList<Supplier> plist2 = new PhoneList<Supplier>();
    plist2.Add(new Supplier("Global Hardware", "555-8834"));
    plist2.Add(new Supplier("Computer Warehouse", "555-9256"));
    plist2.Add(new Supplier("NetworkCity", "555-2564"));
    try {
      // Find the name of a supplier given a number.
      Supplier sp = plist2.FindByNumber("555-2564");
      Console.WriteLine(sp.Name + ": " + sp.Number);
    } catch(NotFoundException) {
        Console.WriteLine("Not Found");
    }

    // The following declaration is invalid because EmailFriend
    // does NOT inherit PhoneNumber.
//    PhoneList<EmailFriend> plist3 =
//        new PhoneList<EmailFriend>(); // Error!
  }
}

The output from the program is shown here:

Gary: 555-6756 (work)

NetworkCity: 555-2564

You might want to try experimenting with this program a bit. For example, try creating different types of telephone lists. Also, try using IsWorkNumber from within PhoneList. As you will see, the compiler won’t let you do it. The reason is that IsWorkNumber is a property defined by Friend, not by PhoneNumber. Thus, PhoneList has no knowledge of it.

Using an Interface Constraint

The interface constraint enables you to specify an interface that a type argument must implement. The interface constraint serves the same two important purposes as the base class constraint. First, it lets you use the members of the interface within the generic class. Second, it ensures that only type arguments that implement the specified interface are used. This means that for any given interface constraint, the type argument must be either the interface or a type that implements that interface.

The interface constraint uses this form of the where clause:

where T: interface-name

Here, T is the name of the type parameter, and interface-name is the name of the interface. More than one interface can be specified by using a comma-separated list. If a constraint includes both a base class and interface, then the base class must be listed first.

The following program illustrates the interface constraint by reworking the telephone list example shown in the previous section. In this version, the PhoneNumber class has been converted into an interface called IPhoneNumber. This interface is then implemented by Friend and Supplier.

// Use an interface constraint.

using System;

// A custom exception that is thrown if a name or number is not found.
class NotFoundException : Exception {
  /* Implement all of the Exception constructors. Notice that
     the constructors simply execute the base class constructor.
     Because NotFoundException adds nothing to Exception,
     there is no need for any further actions. */
  public NotFoundException() : base() { }
  public NotFoundException(string message) : base(message) { }
  public NotFoundException(string message, Exception innerException) :
    base(message, innerException) { }
  protected NotFoundException(
    System.Runtime.Serialization.SerializationInfo info,
    System.Runtime.Serialization.StreamingContext context) :
      base(info, context) { }
}

// An interface that supports a name and phone number.
public interface IPhoneNumber {

  string Number {
    get;
    set;
  }

  string Name {
    get;
    set;
  }
}

// A class of phone numbers for friends.
// It implements IPhoneNumber.
class Friend : IPhoneNumber {

  public Friend(string n, string num, bool wk) {
     Name = n;
    Number = num;

    IsWorkNumber = wk;
  }

  public bool IsWorkNumber { get; private set; }

  // Implement IPhoneNumber.
  public string Number { get; set; }
  public string Name { get; set; }

  // ...
}

// A class of phone numbers for suppliers.
class Supplier : IPhoneNumber {

  public Supplier(string n, string num) {
    Name = n;
    Number = num;
  }
  // Implement IPhoneNumber.
  public string Number { get; set; }
  public string Name { get; set; }

  // ...
}

// Notice that this class does not implement IPhoneNumber.
class EmailFriend {
  // ...
}

// PhoneList can manage any type of phone list
// as long as it implements IPhoneNumber.
class PhoneList<T> where T : IPhoneNumber {
  T[] phList;
  int end;

  public PhoneList() {
    phList = new T[10];
    end = 0;
  }

  public bool Add(T newEntry) {
    if(end == 10) return false;

    phList[end] = newEntry;
    end++;

    return true;
  }

  // Given a name, find and return the phone info.
  public T FindByName(string name) {

     for(int i=0; i<end; i++) {
      // Name can be used because it is a member of
      // IPhoneNumber, which is the interface constraint.
      if(phList[i].Name == name)
         return phList[i];
     }

    // Name not in list.
    throw new NotFoundException();
  }

  // Given a number, find and return the phone info.
  public T FindByNumber(string number) {

     for(int i=0; i<end; i++) {
      // Number can be used because it is also a member of
      // IPhoneNumber, which is the interface constraint.
      if(phList[i].Number == number)
         return phList[i];
     }
    // Number not in list.
    throw new NotFoundException();
  }

  // ...
}

// Demonstrate interface constraints.
class UseInterfaceConstraint {
  static void Main() {

    // The following code is OK because Friend
    // implements IPhoneNumber.
    PhoneList<Friend> plist = new PhoneList<Friend>();
    plist.Add(new Friend("Tom", "555-1234", true));
    plist.Add(new Friend("Gary", "555-6756", true));
    plist.Add(new Friend("Matt", "555-9254", false));

    try {
      // Find the number of a friend given a name.
      Friend frnd = plist.FindByName("Gary");

      Console.Write(frnd.Name + ": " + frnd.Number);

      if(frnd.IsWorkNumber)
        Console.WriteLine(" (work)");
      else
        Console.WriteLine();
    } catch(NotFoundException) {
      Console.WriteLine("Not Found");
     }
    Console.WriteLine();

    // The following code is also OK because Supplier
    // implements IPhoneNumber.
    PhoneList<Supplier> plist2 = new PhoneList<Supplier>();
    plist2.Add(new Supplier("Global Hardware", "555-8834"));
     plist2.Add(new Supplier("Computer Warehouse", "555-9256"));
    plist2.Add(new Supplier("NetworkCity", "555-2564"));

     try {
       // Find the name of a supplier given a number.
       Supplier sp = plist2.FindByNumber("555-2564");
       Console.WriteLine(sp.Name + ": " + sp.Number);
     } catch(NotFoundException) {
         Console.WriteLine("Not Found");
     }

     // The following declaration is invalid because EmailFriend
     // does NOT implement IPhoneNumber.
//    PhoneList<EmailFriend> plist3 =
//        new PhoneList<EmailFriend>(); // Error!
  }
}

In this version of the program, the interface constraint specified by PhoneList requires that a type argument implement the IPhoneList interface. Because both Friend and Supplier implement IPhoneList, they are valid types to be bound to T. However, EmailFriend does not implement IPhoneList and cannot be bound to T. To prove this, remove the comment symbols from the last two lines in Main( ). As you will see, the program will not compile.

Using the new( ) Constructor Constraint

The new( ) constructor constraint enables you to instantiate an object of a generic type. Normally, you cannot create an instance of a generic type parameter. However, the new( ) constraint changes this because it requires that a type argument supply a public parameterless constructor. This can be the default constructor provided automatically when no explicit constructor is declared or a parameterless constructor explicitly defined by you. With the new( ) constraint in place, you can invoke the parameterless constructor to create an object.

Here is a simple example that illustrates the use of new( ):

// Demonstrate a new() constructor constraint.

using System;

class MyClass {

  public MyClass() {
    // ...
  }

  //...
}

class Test<T> where T : new() {
  T obj;

  public Test() {
    // This works because of the new() constraint.
    obj = new T(); // create a T object
  }

  // ...
}

class ConsConstraintDemo {
  static void Main() {

    Test<MyClass> x = new Test<MyClass>();
  }
}

First, notice the declaration of the Test class, shown here:

class Test<T> where T : new() {

Because of the new( ) constraint, any type argument must supply a parameterless constructor.

Next, examine the Test constructor, shown here:

public Test() {
  // This works because of the new() constraint.
  obj = new T(); // create a T object
}

A new object of type T is created and a reference to it is assigned to obj. This statement is valid only because the new( ) constraint ensures that a constructor will be available. To prove this, try removing the new( ) constraint and then attempt to recompile the program. As you will see, an error will be reported.

In Main( ), an object of type Test is instantiated, as shown here:

Test<MyClass> x = new Test<MyClass>();

Notice that the type argument is MyClass, and that MyClass defines a parameterless constructor. Thus, it is valid for use as a type argument for Test. It must be emphasized that it was not necessary for MyClass to explicitly declare a parameterless constructor. Its default constructor would also satisfy the constraint. However, if a class needs other constructors in addition to a parameterless one, then it would be necessary to also explicitly declare a parameterless version, too.

There are three important points about using new( ). First, it can be used with other constraints, but it must be the last constraint in the list. Second, new( ) allows you to construct an object using only the parameterless constructor, even when other constructors are available. In other words, it is not permissible to pass arguments to the constructor of a type parameter. Third, you cannot use new( ) in conjunction with a value type constraint, described next.

The Reference Type and Value Type Constraints

The next two constraints enable you to indicate that a type argument must be either a reference type or a value type. These are useful in the few cases in which the difference between reference and value types is important to generic code. Here is the general form of the reference type constraint:

where T: class

In this form of the where clause, the keyword class specifies that T must be a reference type. Thus, an attempt to use a value type, such as int or bool, for T will result in a compilation error.

Here is the general form of the value type constraint:

where T: struct

In this case, the keyword struct specifies that T must be a value type. (Recall that structures are value types.) Thus, an attempt to use a reference type, such as string, for T will result in a compilation error. In both cases, when additional constraints are present, class or struct must be the first constraint in the list.

Here is an example that demonstrates the reference type constraint:

// Demonstrate a reference constraint.

using System;

class MyClass {
  //...
}

// Use a reference constraint.
class Test<T> where T : class {
  T obj;

  public Test() {
    // The following statement is legal only
    // because T is guaranteed to be a reference
    // type, which can be assigned the value null.
    obj = null;
  }

  // ...
}

class ClassConstraintDemo {
  static void Main() {

    // The following is OK because MyClass is a class.
    Test<MyClass> x = new Test<MyClass>();

    // The next line is in error because int is a value type.
//    Test<int> y = new Test<int>();
  }
}

First, notice how Test is declared:

class Test<T> where T : class {

The class constraint requires that any type argument for T be a reference type. In this program, this is necessary because of what occurs inside the Test constructor:

public Test() {
  // The following statement is legal only
  // because T is guaranteed to be a reference
  // type, which can be assigned the value null.
  obj = null;
}

Here, obj (which is of type T) is assigned the value null. This assignment is valid only for reference types. As a general rule, you cannot assign null to a value type. (The exception to this rule is the nullable type, which is a special structure type that encapsulates a value type and allows the value null. See Chapter 20 for details.) Therefore, without the constraint, the assignment would not have been valid and the compile would have failed. This is one case in which the difference between value types and reference types might be important to a generic routine.

The value type constraint is the complement of the reference type constraint. It simply ensures that any type argument is a value type, including a struct or an enum. (In this context, a nullable type is not considered a value type.) Here is an example:

// Demonstrate a value type constraint.

using System;

struct MyStruct {
  //...
}

class MyClass {
  // ...
}

class Test<T> where T : struct {
  T obj;

  public Test(T x) {
    obj = x;
  }

  // ...
}

class ValueConstraintDemo {
  static void Main() {

    // Both of these declarations are legal.

    Test<MyStruct> x = new Test<MyStruct>(new MyStruct());

    Test<int> y = new Test<int>(10);

    // But, the following declaration is illegal!
//    Test<MyClass> z = new Test<MyClass>(new MyClass());
  }
}

In this program, Test is declared as shown here:

class Test<T> where T : struct {

Because T of Test now has the struct constraint, T can be bound only to value type arguments. This means that Test<MyStruct> and Test<int> are valid, but Test<MyClass> is not. To prove this, try removing the comment symbols from the start of the last line in the program and recompiling. An error will be reported.

Using a Constraint to Establish a Relationship Between Two Type Parameters

There is a variation of the base class constraint that allows you to establish a relationship between two type parameters. For example, consider the following generic class declaration:

class Gen<T, V> where V : T {

In this declaration, the where clause tells the compiler that the type argument bound to V must be identical to or inherit from the type argument bound to T. If this relationship is not present when an object of type Gen is declared, then a compile-time error will result. A constraint that uses a type parameter, such as that just shown, is called a naked type constraint. The following example illustrates this constraint:

// Create relationship between two type parameters.

using System;

class A {
  //...
}

class B : A {
  // ...
}

// Here, V must be or inherit from T.
class Gen<T, V> where V : T {
  // ...
}

class NakedConstraintDemo {
  static void Main() {

    // This declaration is OK because B inherits A.
    Gen<A, B> x = new Gen<A, B>();

    // This declaration is in error because
    // A does not inherit B.
//    Gen<B, A> y = new Gen<B, A>();
  }
}

First, notice that class B inherits class A. Next, examine the two Gen declarations in Main( ). As the comments explain, the first declaration

Gen<A, B> x = new Gen<A, B>();

is legal because B inherits A. However, the second declaration

// Gen<B, A> y = new Gen<B, A>();

is illegal because A does not inherit B.

Using Multiple Constraints

There can be more than one constraint associated with a type parameter. When this is the case, use a comma-separated list of constraints. In this list, the first constraint must be class or struct (if present) or the base class (if one is specified). It is illegal to specify both a class or struct constraint and a base class constraint. Next in the list must be any interface constraints. The new( ) constraint must be last. For example, this is a valid declaration.

class Gen<T> where T : MyClass, IMyInterface, new() { // ...

In this case, T must be replaced by a type argument that inherits MyClass, implements IMyInterface, and has a parameterless constructor.

When using two or more type parameters, you can specify a constraint for each parameter by using a separate where clause. Here is an example:

// Use multiple where clauses.

using System;

// Gen has two type arguments and both have a where clause.
class Gen<T, V> where T : class
                where V : struct {
  T ob1;
  V ob2;

  public Gen(T t, V v) {
     ob1 = t;
     ob2 = v;
  }
}

class MultipleConstraintDemo {
  static void Main() {
    // This is OK because string is a class and
    // int is a value type.
    Gen<string, int> obj = new Gen<string, int>("test", 11);

    // The next line is wrong because bool is not
    // a reference type.
//    Gen<bool, int> obj = new Gen<bool, int>(true, 11);
  }
}

In this example, Gen takes two type arguments and both have a where clause. Pay special attention to its declaration:

class Gen<T, V> where T : class
                where V : struct {

Notice the only thing that separates the first where clause from the second is whitespace. No other punctuation is required or valid.

Creating a Default Value of a Type Parameter

When writing generic code, there will be times when the difference between value types and reference types is an issue. One such situation occurs when you want to give a variable of a type parameter a default value. For reference types, the default value is null. For non-struct value types, the default value is 0, or false for bool. The default value for a struct is an object of that struct with all fields set to their defaults. Thus, trouble occurs if you want to give a variable of a type parameter a default value. What value would you use: null, 0, false, or something else?

For example, given a generic class called Test declared like this:

class Test<T> {
  T obj;
  // ...

if you want to give obj a default value, neither

obj = null; // works only for reference types

nor

obj = 0; // works only for numeric types and enums

works in all classes.

The solution to this problem is to use another form of default, shown here:

default(type)

This is the operator form of default, and it produces a default value of the specified type, no matter what type is used. Thus, continuing with the example, to assign obj a default value of type T, you would use this statement:

obj = default(T);

This will work for all type arguments, whether they are value or reference types.

Here is a short program that demonstrates default:

// Demonstrate the default operator.

using System;

class MyClass {
  //...
}

// Construct a default value of T.
class Test<T> {
  public T obj;

  public Test() {
    // The following statement would work only for reference types.
//    obj = null; // can’t use

    // The following statement will work only for numeric value types.
//    obj = 0; // can’t use
    // This statement works for both reference and value types.
    obj = default(T); // Works!
  }

  // ...
}

class DefaultDemo {
  static void Main() {

     // Construct Test using a reference type.
     Test<MyClass> x = new Test<MyClass>();

     if(x.obj == null)
       Console.WriteLine("x.obj is null.");

     // Construct Test using a value type.
     Test<int> y = new Test<int>();

     if(y.obj == 0)
       Console.WriteLine("y.obj is 0.");
  }
}

The output is shown here:

x.obj is null.
y.obj is 0.

Generic Structures

C# allows you to create generic structures. The syntax is the same as for generic classes. For example, in the following program, the XY structure, which stores X, Y coordinates, is generic:

// Demonstrate a generic struct.
using System;

// This structure is generic.
struct XY<T> {
  T x;
  T y;

  public XY(T a, T b) {
     x = a;
     y = b;
  }

  public T X {
     get { return x; }
     set { x = value; }
  }

  public T Y {
     get { return y; }
    set { y = value; }
  }
}

class StructTest {
  static void Main() {
    XY<int> xy = new XY<int>(10, 20);
    XY<double> xy2 = new XY<double>(88.0, 99.0);

    Console.WriteLine(xy.X + ", " + xy.Y);

    Console.WriteLine(xy2.X + ", " + xy2.Y);
  }
}

The output is shown here:

10, 20
88, 99

Like generic classes, generic structures can have constraints. For example, this version of XY restricts type arguments to value types:

struct XY<T> where T : struct {
// ...

Creating a Generic Method

As the preceding examples have shown, methods inside a generic class can make use of a class’ 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, uint 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 {
  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(strings 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(strings 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 element 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, uint 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 element 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 comments 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 element 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-param-list> (param-list) { // ...

In all cases, type-param-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);

You will need to explicitly specify the type when the compiler cannot infer the type for the T parameter or if you want to override the type inference.

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, uint 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!

Generic Delegates

Like methods, delegates can also be generic. To declare a generic delegate, use this general form:

delegate ret-type delegate-name <type-parameter-list> (arg-list);

Notice the placement of the type parameter list. It immediately follows the delegate’s name. The advantage of generic delegates is that they let you define, in a type-safe manner, a generalized form that can then be matched to any compatible method.

The following program demonstrates a generic delegate called SomeOp that has one type parameter called T. It returns type T and takes an argument of type T.

// A simple generic delegate.

using System;

// Declare a generic delegate.
delegate T SomeOp<T>(T v);

class GenDelegateDemo {
  // Return the summation of the argument.
  static int Sum(int v) {
    int result = 0;
    for(int i=v; i>0; i--)
       result += i;

     return result;
  }

  // Return a string containing the reverse of the argument.
  static string Reflect(string str) {
    string result = "";

     foreach(char ch in str)
       result = ch + result;

     return result;
  }

  static void Main() {
     // Construct an int delegate.
    SomeOp<int> intDel = Sum;
     Console.WriteLine(intDel(3));

    // Construct a string delegate.
    SomeOp<string> strDel = Reflect;
    Console.WriteLine(strDel("Hello"));
  }
}

The output is shown here:

6
olleH

Let’s look closely at this program. First, notice how the SomeOp delegate is declared:

delegate T SomeOp<T>(T v);

Notice that T can be used as the return type even though the type parameter T is specified after the name SomeOp.

Inside GenDelegateDemo, the methods Sum( ) and Reflect( ) are declared, as shown here:

static int Sum(int v) {

static string Reflect(string str) {

The Sum( ) method returns the summation of the integer value passed as an argument. The Reflect( ) method returns a string that is the reverse of the string passed as an argument.

Inside Main( ), a delegate called intDel is instantiated and assigned a reference to Sum( ):

SomeOp<int> intDel = Sum;

Because Sum( ) takes an int argument and returns an int value, Sum( ) is compatible with an int instance of SomeOp.

In similar fashion, the delegate strDel is created and assigned a reference to Reflect( ):

SomeOp<string> strDel = Reflect;

Because Reflect( ) takes a string argument and returns a string result, it is compatible with the string version of SomeOp.

Because of the type safety inherent in generics, you cannot assign incompatible methods to delegates. For example, assuming the preceding program, the following statement would be in error:

SomeOp<int> intDel = Reflect; // Error!

Because Reflect( ) takes a string argument and returns a string result, it cannot be assigned to an int version of SomeOp.

Generic Interfaces

In addition to generic classes and methods, you can also have generic interfaces. Generic interfaces are specified just like generic classes. Here is an example that reworks the ISeries interface developed in Chapter 12. (Recall that ISeries defines the interface to a class that generates a series of numbers.) The data type upon which it operates is now specified by a type parameter.

// Demonstrate a generic interface.

using System;

public interface ISeries<T> {
  T GetNext(); // return next element in series
  void Reset(); // restart the series
  void SetStart(T v); // set the starting element
}

// Implement ISeries.
class ByTwos<T> : ISeries<T> {
  T start;
  T val;

  // This delegate defines the form of a method
  // that will be called when the next element in
  // the series is needed.
  public delegate T IncByTwo(T v);

  // This delegate reference will be assigned the
  // method passed to the ByTwos constructor.
  IncByTwo incr;

  public ByTwos(IncByTwo incrMeth) {
    start = default(T);
    val = default(T);
    incr = incrMeth;
  }
  public T GetNext() {
    val = incr(val);
    return val;
  }

  public void Reset() {
     val = start;
  }

  public void SetStart(T v) {
     start = v;
     val = start;
  }
}

class ThreeD {
  public int x, y, z;

  public ThreeD(int a, int b, int c) {
     x = a;
     y = b;
     z = c;
  }
}

class GenIntfDemo {
  // Define plus two for int.
  static int IntPlusTwo(int v) {
    return v + 2;
  }

  // Define plus two for double.
  static double DoublePlusTwo(double v) {
    return v + 2.0;
  }

  // Define plus two for ThreeD.
  static ThreeD ThreeDPlusTwo(ThreeD v) {
    if(v==null) return new ThreeD(0, 0, 0);
    else return new ThreeD(v.x + 2, v.y + 2, v.z + 2);
  }

  static void Main() {

    // Demonstrate int series.
    ByTwos<int> intBT = new ByTwos<int>(IntPlusTwo);

    for(int i=0; i < 5; i++)
      Console.Write(intBT.GetNext() + "  ");

    Console.WriteLine();
     // Demonstrate double series.
    ByTwos<double> dblBT = new ByTwos<double>(DoublePlusTwo);

    dblBT.SetStart(11.4);

    for(int i=0; i < 5; i++)
      Console.Write(dblBT.GetNext() + "  ");

     Console.WriteLine();

    // Demonstrate ThreeD series.
    ByTwos<ThreeD> ThrDBT = new ByTwos<ThreeD>(ThreeDPlusTwo);

    ThreeD coord;
    for(int i=0; i < 5; i++) {
      coord = ThrDBT.GetNext();
       Console.Write(coord.x + "," +
                     coord.y + "," +
                     coord.z + "  ");
    }

     Console.WriteLine();
  }
}

The output is shown here:

2  4  6  8  10
13.4  15.4  17.4  19.4  21.4
0,0,0  2,2,2  4,4,4  6,6,6  8,8,8

There are several things of interest in the preceding example. First, notice how ISeries is declared:

public interface ISeries<T> {

As mentioned, a generic interface uses a syntax similar to that of a generic class.

Now, notice how ByTwos, which implements ISeries, is declared:

class ByTwos<T> : ISeries<T> {

The type parameter T is specified by ByTwos and is also specified in ISeries. This is important. A class that implements a generic version of a generic interface must, itself, be generic. For example, the following declaration would be illegal because T is not defined:

class ByTwos : ISeries<T> { // Wrong!

The type argument required by the ISeries interface must be passed to ByTwos. Otherwise, there is no way for the interface to receive the type argument.

Next, the current value of the series, val, and the starting value, start, are declared to be objects of the generic type T. Then, a delegate called IncByTwo is declared. This delegate defines the form of a method that will be used to increase an object of type T by two. In order for ByTwos to work with any type of data, there must be some way to define what an increase by two means for each type of data. This is achieved by passing to the ByTwos constructor a reference to a method that performs an increase by two. This reference is stored in incr. When the next element in the series is needed, that method is called through the incr delegate to obtain the next value in the series.

Notice the class ThreeD. It encapsulates three-dimensional (X, Z, Y) coordinates. It is used to demonstrate ByTwos on a class type.

In GenIntfDemo, three increment methods are declared; one for int, one for double, and one for objects of type ThreeD. These are passed to the ByTwos constructor when objects of their respective types are created. Pay special attention to ThreeDPlusTwo( ), shown here:

// Define plus two for ThreeD.
static ThreeD ThreeDPlusTwo(ThreeD v) {
  if(v==null) return new ThreeD(0, 0, 0);
  else return new ThreeD(v.x + 2, v.y + 2, v.z + 2);
}

Notice that it first checks if v is null. If it is, then it returns a new ThreeD object in which all fields are set to zero. The reason for this is that v is set to default(T) by the ByTwos constructor. This value is zero for value types and null for object types. Thus, (unless SetStart( ) has been called) for the first increment, v will contain null instead of a reference to an object. This means that for the first increment, a new object is required.

A type parameter for a generic interface can have constraints in the same way as it can for a generic class. For example, this version of ISeries restricts its use to reference types:

public interface ISeries<T> where T : class {

When this version of ISeries is implemented, the implementing class must also specify the same constraint for T, as shown here:

class ByTwos<T> : ISeries<T> where T : class {

Because of the reference constraint, this version of ISeries cannot be used on value types. Thus, in the preceding program, only ByTwos<ThreeD> would be valid. ByTwos<int> and ByTwos<double> would be invalid.

Comparing Instances of a Type Parameter

Sometimes you will want to compare two instances of a type parameter. For example, you might want to write a generic method called IsIn( ) that returns true if some value is contained within an array. To accomplish this, you might first try something like this:

// This won’t work!
public static bool IsIn<T>(T what, T[] obs) {
  foreach(T v in obs)
    if(v == what) // Error!
      return true;

  return false;
}

Unfortunately, this attempt won’t work. Because T is a generic type, the compiler has no way to know precisely how two objects should be compared for equality. Should a bitwise comparison be done? Should only certain fields be compared? Should reference equality be used? The compiler has no way to answer these questions. Fortunately, there is a solution.

To enable two objects of a generic type parameter to be compared, they must implement the IComparable or IComparable<T>, and/or IEquatable<T> interfaces. Both versions of IComparable define the CompareTo( ) method and IEquatable<T> defines the Equals( ) method. The IComparable interfaces are intended for use when you need to determine the relative order of two objects. IEquatable is used for determining the equality of two objects. These interfaces are defined by the System namespace, and they are implemented by all of C#’s built-in types, including int, string, and double. They are also easy to implement for classes that you create. Let’s begin with IEquatable<T>.

The IEquatable<T> interface is declared like this:

public interface IEquatable<T>

The type of data being compared is passed as a type argument to T. It defines the Equals( ) method, which is shown here:

bool Equals(T other)

It compares the invoking object to other. It returns true if the two objects are equal and false otherwise.

When implementing IEquatable<T>, you will usually also need to override GetHashCode( ) and Equals(Object) defined by Object, so they act in a manner compatible with your implementation of Equals( ). The program that follows shows an example.

Using IEquatable<T>, here is a corrected version of IsIn( ):

// Require IEquatable<T> interface.
public static bool IsIn<T>(T what, T[] obs) where T : IEquatable<T> {
  foreach(T v in obs)
    if(v.Equals(what)) // Uses Equals().
      return true;

  return false;
}

Notice the use of the constraint

where T : IEquatable<T>

This constraint ensures that only types that implement IEquatabl e are valid type arguments for IsIn( ). Inside IsIn( ), Equals( ) is used to determine if one object is equal to another.

To determine the relative order of two elements, use the IComparable interface. It has two forms: generic and non-generic. The generic form has the advantage of being type-safe, so it is the form used here. IComparable<T> is declared like this:

public interface IComparable<T>

The type of data being compared is passed as a type argument to T. It defines CompareTo( ), which is shown here:

int CompareTo(T other)

It compares the invoking object to other. It returns zero if the two objects are equal, a positive value if the invoking object is greater than other, and a negative value if the invoking object is less than other.

To use CompareTo( ), you must specify a constraint that requires the type argument to implement the IComparable<T> interface. Then, when you need to compare two instances of the type parameter, simply call CompareTo( ).

Here is an example that uses IComparable<T>. It is a method called InRange( ) that returns true if an object is within the range of elements contained in a sorted array.

// Require IComparable<T> interface. This method assumes
// a sorted array. It returns true if what is inside the range
// of elements passed to obs.
public static bool InRange<T>(T what, T[] obs) where T : IComparable<T> {
  if(what.CompareTo(obs[0]) < 0 ||
     what.CompareTo(obs[obs.Length-1]) > 0) return false;
  return true;
}

The following program shows IsIn( ) and InRange( ) in action:

// Demonstrate IComparable<T> and IEquatable<T>.

using System;

// Now MyClass implements IComparable<T> and IEquatable<T>.
class MyClass : IComparable<MyClass>, IEquatable<MyClass> {
  public int Val;

  public MyClass(int x) { Val = x; }

  // Implement IComparable<T>.
  public int CompareTo(MyClass other) {
    return Val - other.Val; // Now, no cast is needed.
  }

  // Implement IEquatable<T>.
  public bool Equals(MyClass other) {
    return Val == other.Val;
  }

  // An override of Equals(Object).
  public override bool Equals(Object obj) {
    if(obj is MyClass)
      return Equals((MyClass) obj);
    return false;
  }

  // An override of GetHashCode().
  public override int GetHashCode() {
    return Val.GetHashCode();
  }
}

class CompareDemo {

  // Require IEquatable<T> interface.
  public static bool IsIn<T>(T what, T[] obs) where T : IEquatable<T> {
    foreach(T v in obs)
      if(v.Equals(what)) // Uses Equals()
        return true;

    return false;
  }

  // Require IComparable<T> interface. This method assumes
  // a sorted array. It returns true if what is inside the range
  // of elements passed to obs.
  public static bool InRange<T>(T what, T[] obs) where T : IComparable<T> {
    if(what.CompareTo(obs[0]) < 0 ||
       what.CompareTo(obs[obs.Length-1]) > 0) return false;
    return true;
  }

  // Demonstrate comparisons.
  static void Main() {

    // Use IsIn() with int.
    int[] nums = { 1, 2, 3, 4, 5 };

    if(IsIn(2, nums))
      Console.WriteLine("2 is found.");

    if(IsIn(99, nums))
       Console.WriteLine("This won’t display.");

    // Use IsIn() with MyClass.
    MyClass[] mcs = { new MyClass(1), new MyClass(2),
                      new MyClass(3), new MyClass(4) };

    if(IsIn(new MyClass(3), mcs))
      Console.WriteLine("MyClass(3) is found.");

    if(IsIn(new MyClass(99), mcs))
       Console.WriteLine("This won’t

    // Use InRange() with int.
    if(InRange(2, nums))

       Console.WriteLine("2 is within the range of nums.");
     if(InRange(1, nums))
       Console.WriteLine("1 is within the range of nums.");
     if(InRange(5, nums))
       Console.WriteLine("5 is within the range of nums.");
     if(!InRange(0, nums))
       Console.WriteLine("0 is NOT within the range of nums.");
     if(!InRange(6, nums))
       Console.WriteLine("6 is NOT within the range of nums.");

     // Use InRange() with MyClass.
     if(InRange(new MyClass(2), mcs))
       Console.WriteLine("MyClass(2) is within the range of mcs.");
     if(InRange(new MyClass(1), mcs))
       Console.WriteLine("MyClass(1) is within the range of mcs.");
     if(InRange(new MyClass(4), mcs))
       Console.WriteLine("MyClass(4) is within the range of mcs.");
     if(!InRange(new MyClass(0), mcs))
       Console.WriteLine("MyClass(0) is NOT within the range of mcs.");
     if(!InRange(new MyClass(5), mcs))
       Console.WriteLine("MyClass(5) is NOT within the range of mcs.");
  }
}

The output is shown here:

2 is found.
MyClass(3) is found.
2 is within the range of nums.
1 is within the range of nums.
5 is within the range of nums.
0 is NOT within the range of nums.
6 is NOT within the range of nums.
MyClass(2) is within the range of mcs.
MyClass(1) is within the range of mcs.
MyClass(4) is within the range of mcs.
MyClass(0) is NOT within the range of mcs.
MyClass(5) is NOT within the range of mcs.


NOTE If a type parameter specifies a reference or a base class constraint, then = = and ! = can be applied to instances of that type parameter, but they only test for reference equality. To compare values, you must require IComparable, IComparable<T>, or IEquatable<T>.

Generic Class Hierarchies

Generic classes can be part of a class hierarchy in just the same way as non-generic classes. Thus, a generic class can act as a base class or be a derived class. The key difference between generic and non-generic hierarchies is that in a generic hierarchy, any type arguments needed by a generic base class must be passed up the hierarchy by all derived classes. This is similar to the way that constructor arguments must be passed up a hierarchy.

Using a Generic Base Class

Here is a simple example of a hierarchy that uses a generic base class:

// A simple generic class hierarchy.
using System;

// A generic base class.
class Gen<T> {
  T ob;

  public Gen(T o) {
    ob = o;
  }

  // Return ob.
  public T GetOb() {
    return ob;
  }
}

// A class derived from Gen.
class Gen2<T> : Gen<T> {
  public Gen2(T o) : base(o) {
    // ...
  }
}

class GenHierDemo {
  static void Main() {
    Gen2<string> g2 = new Gen2<string>("Hello");

    Console.WriteLine(g2.GetOb());
  }
}

In this hierarchy, Gen2 inherits the generic class Gen. Notice how Gen2 is declared by the following line:

class Gen2<T> : Gen<T> {

The type parameter T is specified by Gen2 and is also passed to Gen. This means that whatever type is passed to Gen2 will also be passed to Gen. For example, this declaration

Gen2<string> g2 = new Gen2<string>("Hello");

passes string as the type parameter to Gen. Thus, the ob inside the Gen portion of Gen2 will be of type string.

Notice also that Gen2 does not use the type parameter T except to pass it along to the Gen base class. Thus, even if a derived class would otherwise not need to be generic, it still must specify the type parameter(s) required by its generic base class.

Of course, a derived class is free to add its own type parameters, if needed. For example, here is a variation on the preceding hierarchy in which Gen2 adds a type parameter of its own:

// A derived class can add its own type parameters.
using System;

// A generic base class.
class Gen<T> {
  T ob; // declare a variable of type T

  // Pass the constructor a reference of type T.
  public Gen(T o) {
    ob = o;
  }

  // Return ob.
  public T GetOb() {
    return ob;
  }
}

// A derived class of Gen that defines a second
// type parameter, called V.
class Gen2<T, V> : Gen<T> {
  V ob2;

  public Gen2(T o, V o2) : base(o) {
    ob2 = o2;
  }

  public V GetObj2() {
    return ob2;
  }
}

// Create an object of type Gen2.
class GenHierDemo2 {
  static void Main() {

    // Create a Gen2 object for string and int.
    Gen2<string, int> x =
      new Gen2<string, int>("Value is: ", 99);

    Console.Write(x.GetOb());
    Console.WriteLine(x.GetObj2());
  }
}

Notice the declaration of this version of Gen2, which is shown here:

class Gen2<T, V> : Gen<T> {

Here, T is the type passed to Gen, and V is the type that is specific to Gen2. V is used to declare an object called ob2 and as a return type for the method GetObj2( ). In Main( ), a Gen2 object is created in which type parameter T is string, and type parameter V is int. The program displays the following, expected, result:

Value is: 99

A Generic Derived Class

It is perfectly acceptable for a non-generic class to be the base class of a generic derived class. For example, consider this program:

// A non-generic class can be the base class of a generic derived class.
using System;

// A non-generic class.
class NonGen {
  int num;

  public NonGen(int i) {
    num = i;
  }

  public int GetNum() {
    return num;
  }
}

// A generic derived class.
class Gen<T> : NonGen {
  T ob;

  public Gen(T o, int i) : base (i) {
    ob = o;
  }

  // Return ob.
  public T GetOb() {
    return ob;
  }
}

// Create a Gen object.
class HierDemo3 {
  static void Main() {

    // Create a Gen object for string.
    Gen<String> w = new Gen<String>("Hello", 47);

    Console.Write(w.GetOb() + " ");
    Console.WriteLine(w.GetNum());
  }
}

The output from the program is shown here:

Hello 47

In the program, notice how Gen inherits NonGen in the following declaration:

class Gen<T> : NonGen {

Because NonGen is not generic, no type argument is specified. Thus, even though Gen declares the type parameter T, it is not needed by (nor can it be used by) NonGen. Thus, NonGen is inherited by Gen in the normal way. No special conditions apply.

Overriding Virtual Methods in a Generic Class

A virtual method in a generic class can be overridden just like any other method. For example, consider this program in which the virtual method GetOb( ) is overridden:

// Overriding a virtual method in a generic class.
using System;

// A generic base class.
class Gen<T> {
  protected T ob;

  public Gen(T o) {
    ob = o;
  }

  // Return ob. This method is virtual.
  public virtual T GetOb() {
    Console.Write("Gen’s
    return ob;
  }
}

// A derived class of Gen that overrides GetOb().
class Gen2<T> : Gen<T> {

  public Gen2(T o) : base(o) {  }

  // Override GetOb().
  public override T GetOb() {
    Console.Write("Gen2’s
    return ob;
  }
}

// Demonstrate generic method override.
class OverrideDemo {
  static void Main() {

     // Create a Gen object for int.
    Gen<int> iOb = new Gen<int>(88);

    // This calls Gen’s version of GetOb().
     Console.WriteLine(iOb.GetOb());

    // Now, create a Gen2 object and assign its
    // reference to iOb (which is a Gen<int> variable).
    iOb = new Gen2<int>(99);

    // This calls Gen2’s version of GetOb().
    Console.WriteLine(iOb.GetOb());
  }
}

The output is shown here:

Gen’s GetOb(): 88
Gen2’s GetOb(): 99

As the output confirms, the overridden version of GetOb( ) is called for an object of type Gen2, but the base class version is called for an object of type Gen.

Notice one other thing: This line

iOb = new Gen2<int>(99);

is valid because iOb is a variable of type Gen<int>. Thus, it can refer to any object of type Gen<int> or any object of a class derived from Gen<int>, including Gen2<int>. Of course, iOb couldn’t be used to refer to an object of type Gen2<double>, for example, because of the type mismatch.

Overloading Methods That Use Type Parameters

Methods that use type parameters to declare method parameters can be overloaded. However, the rules are a bit more stringent than they are for methods that don’t use type parameters. In general, a method that uses a type parameter as the data type of a parameter can be overloaded as long as the signatures of the two versions differ. This means the type and/or number of their parameters must differ. However, the determination of type difference is not based on the generic type parameter, but on the type argument substituted for the type parameter when a constructed type is created. Therefore, it is possible to overload a method that uses type parameters in such a way that it “looks right,” but won’t work in all specific cases.

For example, consider this generic class:

// Ambiguity can result when overloading methods that
// use type parameters.
//
// This program will not compile.

using System;

// A generic class that contains a potentially ambiguous
// overload of the Set() method.
class Gen<T, V> {
  T ob1;
  V ob2;
  // ...

  // In some cases, these two methods
  // will not differ in their parameter types.
  public void Set(T o) {
     ob1 = o;
  }

  public void Set(V o) {
     ob2 = o;
  }
}

class AmbiguityDemo {
  static void Main() {
    Gen<int, double> ok = new Gen<int, double>();

    Gen<int, int> notOK = new Gen<int, int>();

    ok.Set(10); // is valid, type args differ

    notOK.Set(10); // ambiguous, type args are the same!
  }
}

Let’s examine this program closely. First, notice that Gen declares two type parameters: T and V. Inside Gen, Set( ) is overloaded based on parameters of type T and V, as shown here:

public void Set(T o) {
  ob1 = o;
}

public void Set(V o) {
  ob2 = o;
}

This looks reasonable because T and V appear to be different types. However, this overloading creates a potential ambiguity problem.

As Gen is written, there is no requirement that T and V actually be different types. For example, it is perfectly correct (in principle) to construct a Gen object as shown here:

Gen<int, int> notOK = new Gen<int, int>();

In this case, both T and V will be replaced by int. This makes both versions of Set( ) identical, which is, of course, an error. Thus, when the attempt to call Set( ) on notOK occurs later in Main( ), a compile-time ambiguity error is reported.

In general, you can overload methods that use type parameters as long as there is no constructed type that results in a conflict. Like methods, constructors, operators, and indexers that use type parameters can also be overloaded, and the same rules apply.

Covariance and Contravariance in Generic Type Parameters

In Chapter 15, covariance and contravariance were described as they relate to non-generic delegates. That form of contravariance and covariance is still fully supported by C# 4.0 and is quite useful. However, C# 4.0 expands the covariance and contravariance features to include generic type parameters that are used by generic interfaces and generic delegates. One of their principal uses is to streamline certain types of situations encountered when using generic interfaces and delegates defined by the .NET Framework, and some interfaces and delegates defined by the library have been upgraded to use type parameter covariance and contravariance. Of course, they can also be beneficial in interfaces and delegates that you create.

This section explains the generic type parameter covariance and contravariance mechanisms and shows examples of both.

Using Covariance in a Generic Interface

As it applies to a generic interface, covariance is the feature that enables a method to return a type that is derived from the class specified by a type parameter. In the past, because of the strict type-checking applied to generics, the return type had to match the type parameter precisely. Covariance relaxes this rule in a type-safe way. A covariant type parameter is declared by preceding its name with the keyword out.

To understand the implications of covariance, it is helpful to work through an example. First, here is a very simple interface called IMyCoVarGenIF that uses covariance:

// This generic interface supports covariance.
public interface IMyCoVarGenIF<out T> {
  T GetObject();
}

Pay special attention to the way that the type parameter T is declared. It is preceded by the keyword out. When used in this context, out specifies that T is covariant. Because T is covariant, GetObject( ) can return a reference of type T or a reference of any class derived from T.

Although covariant on T, IMyCoVarGenIF is implemented just like any other generic interface. For example, here it is implemented by MyClass:

// Implement the IMyCoVarGenIF interface.
class MyClass<T> : IMyCoVarGenIF<T> {
  T obj;

  public MyClass(T v) { obj = v; }

  public T GetObject() { return obj; }
}

Notice that out is not specified again in the interface clause of MyClass. Not only is it not needed, but also it would be an error to attempt to specify it again.

Now, assume the following simple class hierarchy:

// Create a simple class hierarchy.
class Alpha {
  string name;
  public Alpha(string n) { name = n; }

  public string GetName() { return name; }
  // ...
}

class Beta : Alpha {
  public Beta(string n) : base(n) { }
  // ...
}

Notice that Beta is derived from Alpha.

Given the foregoing, the following sequence is legal:

// Create a IMyCoVarGenIF reference to a MyClass<Alpha> object.
// This is legal with or without covariance.
IMyCoVarGenIF<Alpha> AlphaRef =
     new MyClass<Alpha>(new Alpha("Alpha #1"));

Console.WriteLine("Name of object referred to by AlphaRef is " +
                  AlphaRef.GetObject().GetName());

// Now create a MyClass<Beta> object and assign it to AlphaRef.
// *** This line is legal because of covariance. ***
AlphaRef = new MyClass<Beta>(new Beta("Beta #1"));

Console.WriteLine("Name of object referred to by AlphaRef is now " +
                  AlphaRef.GetObject().GetName());

First, an IMyCoVarGenIF<Alpha> interface variable called AlphaRef is created and is assigned a reference to a MyClass<Alpha> object. This is legal because MyClass implements IMyCoVarGenIF, and both specify Alpha for a type argument. Next, the name of the object is displayed by calling GetName( ) on the object returned by GetObject( ). Again, this works because the return type of GetName( ) is Alpha (in this case) and T is of type Alpha. Next, AlphaRef is assigned a reference to an instance of MyClass<Beta>. This is legal because Beta is derived from Alpha, and T is covariant in IMyCoVarGenIF. If either of these were not the case, the statement would be illegal.

For your convenience, the entire sequence is assembled into the program shown here:

// Demonstrate generic interface covariance.
using System;

// This generic interface supports covariance.
public interface IMyCoVarGenIF<out T> {
  T GetObject();
}

// Implement the IMyCoVarGenIF interface.
class MyClass<T> : IMyCoVarGenIF<T> {
  T obj;

  public MyClass(T v) { obj = v; }
  public T GetObject() { return obj; }
}

// Create a simple class hierarchy.
class Alpha {
  string name;

  public Alpha(string n) { name = n; }

  public string GetName() { return name; }
  // ...
}

class Beta : Alpha {
  public Beta(string n) : base(n) { }
  // ...
}
class VarianceDemo {
  static void Main() {
    // Create a IMyCoVarGenIF reference to a MyClass<Alpha> object.
    // This is legal with or without covariance.
    IMyCoVarGenIF<Alpha> AlphaRef =
         new MyClass<Alpha>(new Alpha("Alpha #1"));

    Console.WriteLine("Name of object referred to by AlphaRef is " +
                       AlphaRef.GetObject().GetName());

      // Now create a MyClass<Beta> object and assign it to AlphaRef.
     // *** This line is legal because of covariance. ***
     AlphaRef = new MyClass<Beta>(new Beta("Beta #1"));
    Console.WriteLine("Name of object referred to by AlphaRef is now " +
                       AlphaRef.GetObject().GetName());
  }
}

The output is

Name of object referred to by AlphaRef is Alpha #1
Name of object referred to by AlphaRef is now Beta #1

It is important to stress that AlphaRef can be assigned a reference to a MyClass<Beta> object only because T is covariant in IMyCoVarGenIF. To prove this, remove out from in IMyCoVarGenIF’s declaration of T, and then attempt to recompile the program. The compilation will fail because the default strict type-checking will not allow the assignment.

It is possible for one generic interface to be inherited by another. In other words, a generic interface with a covariant type parameter can be extended. For example,

public interface IMyCoVarGenIF2<out T> : IMyCoVarGenIF<T> {
  // ...
}

Notice that out is specified only in the extending interface’s declaration. Specifying out in the base interface clause is not necessary or legal. One last point: it is legal for IMyCoVarGenIF2 to not specify T as covariant. However, doing so eliminates the covariance that extending IMyCoVarGetIF could provide. Of course, making IMyCoVarGenIF2 invariant may be required for some uses.

Here are some restrictions that apply to covariance. A covariant type parameter can be applied only to a method return type. Thus, out cannot be applied to a type parameter that is used to declare a method parameter. Covariance works only with reference types. A covariant type cannot be used as a constraint in an interface method. For example, this interface is illegal:

public interface IMyCoVarGenIF2<out T> {
  void M<V>() where V:T; // Error, covariant T cannot be used as constraint
}

Using Contravariance in a Generic Interface

As it applies to a generic interface, contravariance is the feature that lets a method use an argument whose type is a base class of the type specified by the type parameter for that parameter. In the past, because of the strict type-checking applied to generics, a method’s argument type had to match the type parameter precisely. Contravariance relaxes this rule in a type-safe way. A contravariant type parameter is declared by preceding the type parameter with the keyword in.

To understand the effects of contravariance, we will again work through an example. To begin, here is a contravariant generic interface called IMyContraVarGenIF. Notice that its type parameter T is contravariant and it uses T in the declaration of a method called Show( ).

// This generic interface supports contravariance.
public interface IMyContraVarGenIF<in T> {
  void Show(T obj);
}

Notice that T is specified as contravariant by preceding it with in. Also, notice that the parameter type of obj is T.

Next, MyClass implements IMyContraVarGenIF, as shown here:

// Implement the IMyContraVarGenIF interface.
class MyClass<T> : IMyContraVarGenIF<T> {
    public void Show(T x) { Console.WriteLine(x); }
}

Here, Show( ) simply displays the string representation of x (as obtained by WriteLine( )’s implicit call to ToString( )).

Next, a class hierarchy is declared:

// Create a simple class hierarchy.
class Alpha {
  public override string ToString() {
    return "This is an Alpha object.";
  }
  // ...
}
class Beta : Alpha {
  public override string ToString() {
    return "This is a Beta object.";
  }
  // ...
}

Notice that these versions of Alpha and Beta differ from the previous example for the sake of illustration. Also notice that ToString( ) is overridden to return the type of object.

Given the foregoing, the following sequence is legal:

// Create an IMyContraVarGenIF<Alpha> reference to a
// MyClass<Alpha> object.
// This is legal with or without contravariance.
IMyContraVarGenIF<Alpha> AlphaRef = new MyClass<Alpha>();

// Create an IMyContraVarGenIF<Beta> reference to a
// MyClass<Beta> object.
// This is legal with or without contravariance.
IMyContraVarGenIF<beta> BetaRef = new MyClass<Beta>();

// Create an IMyContraVarGenIF<beta> reference to
// a MyClass<Alpha> object.
// *** This is legal because of contravariance. ***
IMyContraVarGenIF<Beta> BetaRef2 = new MyClass<Alpha>();

// This call is legal with or without contravariance.
BetaRef.Show(new Beta());

// Assign AlphaRef to BetaRef.
//  *** This is legal because of contravariance. ***
BetaRef = AlphaRef;

BetaRef.Show(new Beta());

First, notice that two IMyContraVarGenIF reference variables are created and are assigned references to MyClass objects whose type parameters match that of the interface references. The first uses Alpha. The second uses Beta. These declarations do not require contravariance and are legal in all cases.

Next, an IMyContraVarGenIF<Beta> reference is created, but it is assigned a reference to a MyClass<Alpha> object. This is legal only because T is contravariant.

As you would expect, the next line, which calls BetaRef.Show( ) with a Beta argument, is legal because T in MyClass<Beta> is Beta, and the argument to Show( ) is Beta.

The next line assigns AlphaRef to BetaRef. This is legal only because of contravariance. In this case, BetaRef is of type MyClass<Beta>, but AlphaRef is of type MyClass<Alpha>. Because Alpha is a base class of Beta, contravariance makes this conversion legal. To prove to yourself that contravariance is required in the program, try removing in from the declaration of T in IMyContraVarGenIF. Then attempt to recompile the program. As you will see, errors will result.

For your convenience, all the pieces are assembled into the following program:

// Demonstrate generic interface contravariance.
using System;

// This generic interface supports contravariance.
public interface IMyContraVarGenIF<in T> {
  void Show(T obj);
}
// Implement the IMyContraVarGenIF interface.
class MyClass<T> : IMyContraVarGenIF<T> {
  public void Show(T x) { Console.WriteLine(x); }
}
// Create a simple class hierarchy.
class Alpha {
  public override string ToString() {
    return "This is an Alpha object.";
  }
  // ...
}
class Beta : Alpha {
  public override string ToString() {
    return "This is a Beta object.";
  }
  // ...
}

class VarianceDemo {
  static void Main() {
    // Create an IMyContraVarGenIF<Alpha> reference to a
    // MyClass<Alpha> object.
    // This is legal with or without contravariance.

    IMyContraVarGenIF<Alpha> AlphaRef = new MyClass<Alpha>();
    // Create an IMyContraVarGenIF<beta> reference to a
    // MyClass<Beta> object.
    // This is legal with or without contravariance.
    IMyContraVarGenIF<Beta> BetaRef = new MyClass<Beta>();

    // Create an IMyContraVarGenIF<beta> reference to
    // a MyClass<Alpha> object.
    // *** This is legal because of contravariance. ***

    IMyContraVarGenIF<Beta> BetaRef2 = new MyClass<Alpha>();
    // This call is legal with or without contravariance.
    BetaRef.Show(new Beta());

    // Assign AlphaRef to BetaRef.
    //  *** This is legal because of contravariance. ***
    BetaRef = AlphaRef;
    BetaRef.Show(new Beta());
  }
}

The output is shown here:

This is a Beta object.
This is a Beta object.

A contravariant interface can be extended. The process is similar to that described for extending a covariant interface. To access the contravariant nature of the extended interface, the extending interface must specify in for the type parameter that corresponds to the contravarient type parameter in the base interface. For example,

public interface IMyContraVarGenIF2<in T> : IMyContraVarGenIF<T> {
// ...
}

Notice that in is not required (nor would it be legal) to be specified in the base interface clause. Furthermore, it is not required that IMyContraVarGenIF2 be contravariant. In other words, it is not required that IMyContraVarGenIF2 modify T with in. Of course, any benefits that could result from the contravariant IMyContraVarGen interface would be lost relative to the IMyContraVarGenIF2 interface.

Contravariance works only with reference types, and a contravariant type parameter can be applied only to method arguments. Thus, in cannot be applied to a type parameter that is used for a return type.

Variant Delegates

As explained in Chapter 15, non-generic delegates already support covariance and contravariance as they relate to method return types and parameter types. Beginning with C# 4.0, these features are expanded for generic delegates to include type parameter covariance and contravariance. These features work in a fashion similar to that just described for interfaces.

Here is an example of a contravariant delegate:

// Declare a generic delegate that is contravariant on T.
delegate bool SomeOp<in T>(T obj);

This delegate can be assigned a method whose parameter is T or a class from which T is derived.

Here is an example of a covariant delegate:

// Declare a generic delegate that is covariant on T.
delegate T AnotherOp<out T, V>(V obj);

This delegate can be assigned a method whose return type is T or a class derived from T. In this case, V is simply an invariant type parameter.

The following program puts these delegates into action:

// Demonstrate covariance and contravariance with a generic delegate.

using System;
// Declare a generic delegate that is contravariant on T.
delegate bool SomeOp<in T>(T obj);

// Declare a generic delegate that is covariant on T.
delegate T AnotherOp<out T, V>(V obj);

class Alpha {
  public int Val { get; set; }

  public Alpha(int v) { Val = v; }
}
class Beta : Alpha {
  public Beta(int v) : base(v) { }
}

class GenDelegateVarianceDemo {
  // Return true if obj.Val is even.
  static bool IsEven(Alpha obj) {
     if((obj.Val % 2) == 0) return true;
    return false;
   }

  static Beta ChangeIt(Alpha obj) {
    return new Beta(obj.Val +2);
  }

  static void Main() {
    Alpha objA = new Alpha(4);
    Beta objB = new Beta(9);

    // First demonstrate contravariance.

    // Declare a SomeOp<Alpha> delegate and set it to IsEven.
    SomeOp<Alpha> checkIt = IsEven;

    // Declare a SomeOp<Beta> delegate.
    SomeOp<Beta> checkIt2;

    // Now, assign the SomeOp<Alpha> delegate the SomeOp<Beta> delegate.
    // *** This is legal only because of contravariance. ***
    checkIt2 = checkIt;

    // Call through the delegate.
    Console.WriteLine(checkIt2(objB));

    // Now, demonstrate covariance.

    // First, declare two AnotherOp delegates.
    // Here, the return type is Beta and the parameter type is Alpha.
    // Notice that modifyIt is set to ChangeIt.
    AnotherOp<Beta, Alpha> modifyIt = ChangeIt;

    // Here, the return type is Alpha and the parameter type is Alpha.
    AnotherOp<Alpha, Alpha> modifyIt2;

    // Now, assign modifyIt to modifyIt2.
    // *** This statement is legal only because of covariance. ***
    modifyIt2 = modifyIt;

    // Actually call the method and display the results.
    objA = modifyIt2(objA);
    Console.WriteLine(objA.Val);
  }
}

The output is shown here:

False
6

The comments in the program explain each operation. It is important to stress, however, that for both SomeOp and AnotherOp, the use of in and out, respectively, is necessary for the program to compile. Without these modifiers, compilation errors would result at the indicated lines because no implicit conversions would be available.

How Generic Types Are Instantiated

One question that is often raised when working with generics is whether the use of a generic class leads to code-bloat at runtime. The simple answer is no. The reason is that C# implements generics in a highly efficient manner that creates new constructed types only when they are needed. Here is how the process works.

When a generic class is compiled into MSIL, it retains all of its type parameters in their generic form. At runtime, when a specific instance of the class is required, the JIT compiler constructs a specific, executable code version of the class in which the type parameters are replaced by the type arguments. Each instance of the class that uses the same type arguments will use the same executable code version.

For example, given some generic class called Gen<T>, then all Gen<int> objects will use the same executable code. Thus, code-bloat is reduced and only those versions of the class that are actually used in the program will be created. When a different constructed type is needed, a new version of the class is compiled.

In general, a new executable version of a generic class is created for each constructed type in which the type argument is a value type, such as int or double. Thus, each object of Gen<int> will use one version of Gen and each object of type Gen<double> will use another version of Gen, with each version of Gen tailored to the specific value type. However, there will be only one version of a generic class that handles all cases in which the type argument is a reference type. This is because the size (in bytes) of all references is the same. Thus, only one version is needed to handle all types of references. This optimization also reduces code-bloat.

Some Generic Restrictions

Here are a few restrictions that you need to keep in mind when using generics:

• Properties, operators, and indexers cannot be generic. However, these items can be used in a generic class and can make use of the generic type parameters of that class.

• The extern modifier cannot be applied to a generic method.

• Pointer types cannot be used as type arguments.

• If a generic class contains a static field, then each constructed type has its own copy of that field. This means that each instance of the same constructed type shares the same static field. However, a different constructed type shares a different copy of that field. Thus, a static field is not shared by all constructed types.

Final Thoughts on Generics

Generics are a powerful element of C# because they streamline the creation of type-safe, reusable code. Although the generic syntax can seem a bit overwhelming at first, it will quickly become second nature. Likewise, learning how and when to use constraints takes a bit of practice, but becomes easier over time. Generics are now an integral part of C# programming. It’s worth the effort it takes to master this important feature.