[edit]
Why use generics?
There are mainly two reasons to use generics. These are:- Performance – Collections that store objects use Boxing and Unboxing on data types. This uses a significant amount of overhead, which can give a performance hit. By using generics instead, this performance hit is removed.
- Type Safety – There is no strong type information at compile time as to what is stored in the collection.
public class StackIn order to use this class you can push any object onto the
{
object[] store;
int size;
public void Push(object obj) {...}
public object Pop() {...}
}
Stack
, however when retrieving an object, an explicit cast is required. Stack s = new Stack();Such Boxing and Unboxing operations add performance overhead since they involve dynamic memory allocations and run-time type checks.
s.Push(28);
int j = (int)s.Pop(); //unboxing with explicit int casting
Now imagine what would happen if you pushed a string on the
Stack
as in the following code; Stack s = new Stack();The code will compile, however the problem will not become visible until the code is executed, at which point an
s.Push("Hello World!"); // pushing the string
int i = (int)s.Pop(); // run-time exception will be thrown at this point
InvalidCastException
will be thrown. [edit]
Generic equivalent
If theStack
class above was of a generic type, there would be a compile-time error when pushing the string onto the Stack
. The generic version of the Stack
class looks like the following code; public class Stack
{
// Items are of type T, which is known when you create the object
T[] items;
int count;
public void Push(T item) {...}
// The type of the method Pop will be decided when you create the object
public T Pop() {...}
}
T
is the type for the parameter. Within class you can use T
as if it were a type, this could be anything from a simple data-type such as int
to a complex object such as Car
. In the following example,
string
is given as the type argument for T
: StackThes = new Stack ();
s.Push("Hello World!");
string str = s.Pop();
Stack
type is called a constructed type. In the Stack
type, every occurrence of T
is replaced with the type argument string
. The Push
and Pop
methods of a Stack
operate on string values, making it a compile-time error to push values of other types onto the Stack
, and eliminating the need to explicitly cast values back to their original type when they are retrieved. You can use parameterization not only for classes but also for interfaces, structs, methods and delegates.
interface IComparable
struct HashBucket
static void Sort(T[] arr)
delegate void Action(T arg)
[edit]
Terminology
There are several terms that are used when talking about generics, so it is worth mentioning them here so that when reading other documents, a better understanding can be obtained.[edit]
Type parameters
A type parameter refers to the parameter that is used in the definition of the generic type. In the generic version of the Stack class above, the class accepts one type parameter,T
. [edit]
Type arguments
A type argument refers to the type you specify to use in place of the type parameter. In the following code segment, string is the type argument.Stacks = new Stack ();
[edit]
Open types
Instead of referring to this as a class, generics consider theStack
as an "open type". The term "open" is meant to convey the idea that the type is not fully defined and is "open" to taking on multiple real representations. [edit]
Constructed types
A constructed type represents an instance of an open type. To create a constructed type from the open Stack type, use the following code:StackOrs;
Stacks = new Stack ();
[edit]
Open constructed type
An open constructed type is created when at least one type argument is left unspecified, the parameter is still open to run-time definition. Consider the following type declaration:public class LinkedListThe following are all open constructed types:{ ... }
LinkedListmyList_1;
LinkedListmyList_2;
LinkedListmyList_3;
[edit]
Closed constructed type
A closed constructed type is created by specifying all of the type arguments, therefore they are not open to run-time definition. Consider the following type declaration:public class LinkedListThe following are all closed constructed types:{ ... }
LinkedListmyList_1;
LinkedListmyList_2;
LinkedList
[edit]
Constraints on type parameters
Constraints are used to restrict a generic class to the kinds of types that client code can use for type arguments. If code attempts to instantiate the class with a type that is not allowed by a constraint, then this will result in a compile-time error. Constraints are specified using thewhere
keyword. The following table lists the five types of constraints: Constraint | Description |
---|---|
where T : struct | The type argument, T, must be any value type except Nullable. |
where T : class | The type argument, T, must be a reference type, including any class, interface, delegate, or array type. |
where T : new() | The type argument, T, must have a public no-argument constructor. When used in conjunction with other constraints, the new() constraint must be specified last. |
where T : | The type argument, T, must be or derive from the specified base class. |
where T : | The type argument, T, must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic. |
where T : U | The type argument, T, must be or derive from the argument supplied for U. This is called a naked type constraint. |
[edit]
Generic classes
Generic classes encapsulate operations that are not specific to a particular data type. Generic classes can be the base class to other classes, and can therefore define any number ofvirtual
or abstract
methods. Any derived type must abide by rules to ensure that the nature of the generic abstraction flows through to it. - If a non-generic class extends a generic class, the derived class must specify a type parameter:
// Generic list class.
public class GenericList
{
private ListmyList = new List ();
}
// Derived type specifies the type parameter.
public class MyStringList : GenericList
{
}
- If a generic base class defines generic
virtual
orabstract
methods, the derived type mustoverride
the generic methods using the specified type parameter:
// Generic class with virtual method.
public class GenericList
{
private ListmyList = new List ();
public virtual void PrintList(T data)
{
}
}
public class MyStringList : GenericList
{
// Derived method must substitute the type parameter
public override void PrintList(string data)
{
}
}
- A generic derived type may reuse the type placeholder in its definition. Any constraints placed on the base class must be honoured by the derived type:
// Default constructor constraint.
public class GenericListwhere T : new()
{
private ListmyList = new List ();
public virtual void PrintList(T data)
{
}
}
// Derived type must honour constraint.
public class MyReadOnlyList: GenericList where T : new()
{
public override void PrintList(T data)
{
}
}
[edit]
Generic interfaces
With generic classes it is preferable to use generic interfaces, such as IComparableinterface
is specified as a constraint on a type parameter, only types that implement the interface can be used. For example: public class SortedList: GenericList where T : System.IComparable
{
. . .
}
[edit]
Defining a generic interface
You may define generic interfaces similar to generic classes (including constraints):// Standard InterfaceThe .NET Framework has generic versions of all of the common interfaces.
public interface IPrint
{
void Print();
}
// Generic Interface
public interface MyGenericInterfacewhere T : IPrint
{
void Run(T t);
}
// Class that implements standard Interface
public class MyPrinter : IPrint
{
public void Print()
{
Console.WriteLine("hello");
}
}
// Class that implements Generic Interface
public class Print2 : MyGenericInterface
{
public void Run(MyPrinter t)
{
t.Print();
}
}
// Generic Class that implements Generic Interface with contraints
public class PrintDemo: MyGenericInterface where T : IPrint
{
public void Run(T t)
{
t.Print();
}
}
[edit]
Generic delegates
Suppose that we want a delegate that can update an item, but we are not sure what item is going to be updated. In this scenario, making the delegate generic is the best option. In order to explain this concept, the following code is based on an example from Tod Golding in .Net 2.0 Generics.Imagine if you will that we are creating a piece of software that works with animals, namely cats and dogs. So here are two classes with some properties and a
ToString()
method: public class CatWe want a delegate that can update the age of either a cat or a dog.
{
private string m_name;
public string Name
{
get { return m_name; }
set { m_name = value; }
}
private int m_age;
public int Age
{
get { return m_age; }
set { m_age = value; }
}
public Cat(string name, int age)
{
m_name = name;
m_age = age;
}
public override string ToString()
{
return string.Format("The Cat named {0} is {1} years old",
m_name.ToString(), m_age.ToString());
}
}
public class Dog
{
private string m_name;
public string Name
{
get { return m_name; }
set { m_name = value; }
}
private double m_age;
public double Age
{
get { return m_age; }
set { m_age = value; }
}
public Dog(string name, double age)
{
m_name = name;
m_age = age;
}
public override string ToString()
{
return string.Format("The Dog named {0} is {1} years old",
m_name.ToString(), m_age.ToString());
}
}
public delegate void UpdateAnimalAs we will have many animals, a collection would be a good thing to have, and again to save on overhead and to give type safety, a generic collection would be best.(T value);
public class AnimalsFinally, we need a couple of methods to be called by the delegate to update each of the classes, and some code to test them.: List
{
public void UpdateAnimals(UpdateAnimalupdater)
{
List.Enumerator items = GetEnumerator();
while (items.MoveNext())
{
updater(items.Current);
}
}
public void Print()
{
List.Enumerator items = GetEnumerator();
while (items.MoveNext())
{
Console.WriteLine(items.Current.ToString());
}
}
}
class ProgramThis gives the output:
{
static void Main(string[] args)
{
// Create Lists
AnimalscatList = new Animals ();
catList.Add(new Cat("Tinkerbelle", 6));
catList.Add(new Cat("Felix", 3));
catList.Add(new Cat("Whiskers", 10));
catList.Add(new Cat("Tailz", 14));
AnimalsdogList = new Animals ();
dogList.Add(new Dog("Rufus", 12.1));
dogList.Add(new Dog("Peeps", 3.2));
dogList.Add(new Dog("Hairy McClary", 6.3));
// Cats
catList.Print();
Console.WriteLine("---------------------------");
catList.UpdateAnimals(new UpdateAnimal(UpdateCatAge));
catList.Print();
Console.WriteLine("===========================");
// Dogs
dogList.Print();
Console.WriteLine("---------------------------");
dogList.UpdateAnimals(new UpdateAnimal(UpdateDogAge));
dogList.Print();
}
public static void UpdateCatAge(Cat cat)
{
cat.Age += 1;
}
public static void UpdateDogAge(Dog dog)
{
dog.Age += 0.1;
}
}
Download this sample application (written in C# Express).
[edit]
MSDN references
- Benefits of Generics (C# Programming Guide)
- Constraints on Type Parameters (C# Programming Guide)
-
default
Keyword in Generic Code (C# Programming Guide) - Generics (C# Programming Guide)
- Generics and Reflection (C# Programming Guide)
- Generic Classes (C# Programming Guide)
- Generic Delegates (C# Programming Guide)
- Generic Interfaces (C# Programming Guide)]
- Generic Methods (C# Programming Guide)
- Generics in the .NET Framework Class Library (C# Programming Guide)
- Generics in the Runtime (C# Programming Guide)
- Generics Sample (C#)
- Generic Type Parameters (C# Programming Guide)
- Introduction to Generics (C# Programming Guide)
-
System.Collections.Generic
Namespace
[edit]
0 Comments