|
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.
|