Overview
Generics provide the solution to a limitation in earlier versions of
the common language runtime and the C# language in which generalization is accomplished
by casting types to and from the universal base type Object. By creating a generic class,
you can create a collection that is type-safe at compile-time.
The limitations of using non-generic collection classes can be
demonstrated by writing a short program that makes use of the ArrayList collection class from the .NET Framework base class
library. ArrayList is a highly convenient collection
class that can be used without modification to store any reference or value type.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace Generics
{
class GenericList
{
static void Main(string[] args)
{
// The .NET Framework 1.1 way to create a List
ArrayList list = new
ArrayList();
// Add an integer to the List
list.Add(3);
// Add a string to the List. This will compile,
// but may cause a
Runtime Error!
list.Add("This will trigger a Runtime Error!");
// We cast to int, but this will causes an InvalidCastException
// when encounter a
string in the List.
int t =
(int)list[1];
t = 0;
foreach (int x in
list)
{
t += x;
}
// The .NET Framework 2.0 way to create a List
List<int>
list1 = new List<int>();
// No boxing, no casting
list1.Add(3);
list1.Add(1);
// Compile-time error:
list1.Add("This will
trigger a Compile-time Error!");
// Get the values from the List, no casting
int t1 =
list1[1];
t1 = 0;
foreach (int x in
list1)
{
t1 += x;
}
}
}
}
The .NET Framework 1.1 Way
Any reference or value type that is added to an
ArrayList is implicitly upcast to Object. If the items
are value types, they must be boxed when added to the list, and unboxed when they are
retrieved. Both the casting and the boxing and unboxing operations degrade performance;
the effect of boxing and unboxing can be quite significant in scenarios where you must
iterate over large collections.
The .NET Framework 2.0 Way
For client code, the only added syntax with List<T> compared to ArrayList is the type argument in
the declaration and instantiation. In return for this slightly greater coding complexity,
you can create a list that is not only safer than ArrayList, but also significantly
faster, especially when the list items are value types.
Creating a custom type-safe List
Of course, you can also create custom generic types and
methods to provide your own generalized solutions and design patterns that are type-safe
and efficient. The following code example shows a simple generic linked-list class for
demonstration purposes. (In most cases, it is recommended that you use the List<T> class provided by the .NET Framework class
library, rather than create your own.) The type parameter T is used in several places
where a concrete type would normally be used to indicate the type of the item stored in
the list. It is used in the following way.
using System;
using System.Collections.Generic;
using System.Text;
//
--------------------------------------------------------------------
// The following code example shows a simple generic linked-list
// class for demonstration purposes. (In most cases, it is recommended
// that you use the List<T> class provided by the .NET Framework class
// library, rather than create your own.) The type parameter T is
// used in several places where a concrete type would normally be
// used to indicate the type of the item stored in the list.
// --------------------------------------------------------------------
namespace Generics
{
// Type parameter T in angle brackets
<>
public class CustomList<T>
{
// Fields
private Node head;
// The nested class is
also generic on T
private class Node
{
// Fields
private Node next;
// T as private member data type
private
T data;
// T used in non-generic constructor
public
Node(T pData)
{
next = null;
data = pData;
}
// Properties
public Node Next
{
get
{
return next;
}
set
{
next = value;
}
}
// T as return type of the Property
public T
Data
{
get
{
return data;
}
set
{
data = value;
}
}
}
//
Constructor
public CustomList()
{
head = null;
}
// T as method parameter
type:
public void Add(T pType)
{
Node n = new
Node(pType);
n.Next = head;
head = n;
}
// Enables foreach on
the List
public IEnumerator<T>
GetEnumerator()
{
Node current =
head;
while (current !=
null)
{
yield return current.Data;
current = current.Next;
}
}
// Provides indexing on
the List
public T this[int index]
{
get
{
int ctr = 0;
Node current = head;
while (current != null && ctr <= index)
{
if (ctr == index)
{
return current.Data;
}
else
{
current = current.Next;
}
++ctr;
}
return default(T);
}
}
// Provides ToString()
on the List
public override string ToString()
{
if (this.head !=
null)
{
return this.head.ToString();
}
else
{
return string.Empty;
}
}
}
// Test the CustomList
class TestCustomList
{
// My own Class of any
Datatype
private class ExampleClass
{
private object o;
public
ExampleClass(object obj)
{
o = obj;
}
public object
objGet
{
get
{
return o;
}
}
}
static void Main()
{
// Declare a List of type int, then loop through the List
CustomList<int> list1 = new CustomList<int>();
for (int x = 0; x <
10; x++)
{
list1.Add(x);
}
foreach (int i in
list1)
{
System.Console.Write(i + " ");
}
System.Console.WriteLine("\nDone\n");
// Declare a List of type string, then loop through the List
CustomList<string> list2 = new CustomList<string>();
list2.Add("Martin");
list2.Add("Zahn");
list2.Add("Oberdiessbach");
foreach (string s in
list2)
{
System.Console.Write(s + " ");
}
System.Console.WriteLine("\nDone\n");
// Use Indexer on the List
string s1 =
list2[1];
// Declare a List of type ExampleClass, then loop through the List
// ExampleClass holds
any DataType.
CustomList<ExampleClass> list3 = new CustomList<ExampleClass>();
ExampleClass a = new
ExampleClass(7);
list3.Add(a);
ExampleClass b = new
ExampleClass("Hello");
list3.Add(b);
foreach (ExampleClass
o in list3)
{
System.Console.Write(o.objGet.ToString() + " ");
}
System.Console.WriteLine("\nDone");
// Use Indexer on the List
ExampleClass c =
list3[0];
System.Console.Write("From Indexer: " + c.objGet.ToString() + "\n");
}
}
}
Generics Conclusion
-
Use generic types to maximize code reuse, type safety,
and performance.
-
The most common use of generics is to create
collection classes.
-
The .NET Framework class library contains several new
generic collection classes in the System.Collections.Generic namespace. These should
be used whenever possible in place of classes such as ArrayList in the
System.Collections namespace.
-
You can create your own generic interfaces, classes,
methods, events and delegates.
-
Generic classes may be constrained to enable access to
methods on particular data types.
-
Information on the types used in a generic data type
may be obtained at run-time by means of reflection.
|