Generics
“Generics are classes, structures, interfaces, and methods that have placeholders for one or more of the types they store or use” (Microsoft 2006). Here is an example of a generic class introduced in the System.Collections.Generic namespace of the .NET Framework 2.0 Class Library:
public class List<T>
Among the methods of that class is this one:
public Add(T item)
Here, T is the placeholder for the type that an instance of the generic class System.Collections.Generic.List<T> will store. In defining an instance of the generic class, one specifies the actual type that the instance will store:
List<string> myListOfStrings = new List<string>();
Then one can use the Add() method of the generic class instance like so:
myListOfStrings.Add("Hello, World");
Evidently, generics enabled the designer of the List<T> class to define a collection of instances of the same unspecified type; in other words, to provide the template for a type-safe collection. A user of List<T> can employ it to contain instances of a type of the user’s choosing, without the designer of List<T> having to know which type the user might choose. Note as well that whereas a type that is derived from a base type is meant to derive some of the functionality it requires from the base, with the remainder still having to be programmed, List<string> comes fully equipped from List<T>.
The class, System.Collections.Generic.List<T>, is referred to as a generic type definition. The placeholder, T, is referred to as a generic type parameter. Declaring
List<string> myListOfStrings;
yields System.Collections.Generic.List<string> as a constructed type, and string as a generic type argument.
Generics can have any number of generic type parameters. For example, System.Collections.Generic.Dictionary<TKey, TValue> has two.
The designer of a generic may use constraints to restrict the types that can be used as generic type arguments. This generic type definition
public class MyGenericType<T> where T: new(), IComparable
constrains the generic type arguments to types with a public, parameter-less constructor that implements the IComparable interface. This less restrictive generic type definition
public class MyGenericType<T> where T: class
merely constrains generic type arguments to reference types.
Both generic and nongeneric types can have generic methods. Here is an example of a nongeneric type with a generic method:
using System; public class Printer { public void Print<T>(T argument) { Console.WriteLine(argument.ToString()); } static void Main(string[] arguments) { Printer printer = new Printer(); printer.Print<string>("Hello, World"); Console.WriteLine("Done"); Console.ReadKey(); } }
In programming a generic, it is often necessary to determine the type of generic argument that has been substituted for a generic type parameter. This revision to the preceding example shows how one can make that determination:
public class Printer { public void Print<T>(T argument) { if(typeof(T) == typeof(string)) { Console.WriteLine(argument); } else { Console.WriteLine(argument.ToString()); } } static void Main(string[] arguments) { Printer printer = new Printer(); printer.Print<string>("Hello, World"); Console.WriteLine("Done"); Console.ReadKey(); } }
A generic interface may be implemented by a generic type or a nongeneric type. Also, both generic and nongeneric types may inherit from generic base types.
public interface IMyGenericInterface<T> { void MyMethod<T>(); } public class MyGenericImplementation<T>: IMyGenericInterface<T> { public void MyMethod<T>() { } } public class MyGenericDescendant<T> : MyGenericImplementation<T> { } public class MyNonGenericImplementation : IMyGenericInterface<string> { public void MyMethod<T>() { } } public class MyNonGenericDescendant : MyGenericImplementation<string> { }