News & UpdatesProgrammingWeb programmingPartnersStore
Book Cover
Buy Now
Projects
Links

C# Tutorial – 30 – Generics II

Generic classes

Generic classes allow class members to use type parameters. They are defined in the same way as generic methods, by adding a type parameter after the class name.

class Point<T>
{
  public T x, y;
}

To instantiate an object from the generic class the standard notation is used, but with the type argument specified after both class names. Note that in contrast to generic methods, a generic class must always be instantiated with the type argument explicitly specified.

Point<short> p = new Point<short>();

Generic class inheritance

Inheritance works slightly differently with generic classes. A generic class can first of all inherit from a non-generic class, also called a concrete class. Second, it can inherit from another generic class that has its type argument specified, a so called closed constructed base class. Finally, it can inherit from an open constructed base class, which is a generic class that has its type argument left unspecified.

class BaseConcrete  {}
class BaseGeneric<T>{}
 
class Gen1<T> : BaseConcrete    {} // concrete
class Gen2<T> : BaseGeneric<int>{} // closed constructed
class Gen3<T> : BaseGeneric<T>  {} // open constructed

A generic class that inherits from an open constructed base class must define all of the base class’s type arguments, even if the derived generic class does not need them. This is because only the child class’s type arguments can be sent along when the child class is instantiated.

class BaseMultiple<T, U, V> {}
class Gen4<T, U> : BaseMultiple<T, U, int> {}

This also means that a non-generic class can only inherit from a closed constructed base class, and not from an open one, because a non-generic class cannot specify any type arguments when it is instantiated.

class Con1 : BaseGeneric<int> {} // ok
class Con2 : BaseGeneric<T> {}   // error

Generic interfaces

Interfaces that are declared with type parameters become generic interfaces. Generic interfaces have the same two purposes as regular interfaces. They are either created to expose members of a class that will be used by other classes, or to force a class to implement a specific functionality. When a generic interface is implemented, the type argument must be specified. The generic interface can be implemented by both generic and non-generic classes.

// Generic functionality interface
interface IGenericCollection<T> 
{ 
  void store(T t); 
}
 
// Non-generic class implementing generic interface
class Box : IGenericCollection<int> 
{
  public int myBox;
  public void store(int i) { myBox = i; }
}
 
// Generic class implementing generic interface
class GenericBox<T> : IGenericCollection<T>
{
  public T myBox;
  public void store(T t) { myBox = t; }
}

Generic delegates

A delegate can be defined with type parameters. As an example, the generic delegate below uses its type parameter to specify the referable method’s parameter. From this delegate type a delegate object can be created that can refer to any void method that takes a single argument, regardless of its type.

class MyClass
{
  public delegate void MyDelegate<T>(T arg);
 
  public void Print(string s) 
  { 
    System.Console.Write(s); 
  }
 
  static void Main()
  {
    MyDelegate<string> d = Print;
  }
}

Generic events

Generic delegates can be used to define generic events. For example, instead of using the typical design pattern where the sender of the event is of the Object type, a type parameter can allow the senders actual type to be specified. This will make the argument strongly-typed, which allows the compiler to enforce that the correct type is used for that argument.

delegate void MyDelegate<T, U>(T sender, U eventArgs);
event MyDelegate<MyClass, System.EventArgs> myEvent;

Generics and Object

In general, using the Object type as a universal container should be avoided. The reason why Object containers, such as the ArrayList, exist in the .NET class library is because generics were not introduced until C# 2.0. When compared with the Object type, generics not only ensure type safety at compile-time, but they also remove the performance overhead associated with boxing and unboxing value types into an Object container.

// Object container class
class MyBox { public object o; }
 
// Generic container class
class MyBox<T> { public T o; }
 
class MyClass
{
  static void Main()
  {
    // .NET object container
    System.Collections.ArrayList a;  
 
    // .NET generic container (preferred)
    System.Collections.Generic.List<int> b;
  }
}

If you like this tutorial please +1 it:


Leave a Reply