Tuesday, December 21, 2010

Generics in Depth

Collections and Dictionaries


Download Source Code here

The System.Collections and System.Collections.Specialized namespaces contain a number
of classes to meet varying requirements for storing groups of related objects.



Collections
A collection is any class that allows for gathering items into lists and for iterating
through those items. The .NET Framework includes the following collection classes:
 ArrayList A simple collection that can store any type of object. ArrayList
instances expand to any required capacity.
Queue A first-in, first-out (FIFO) collection. You might use a Queue on a messaging
server to store messages temporarily before processing or to track customer orders
that need to be processed on a first-come, first-serve basis.
 Stack A last-in, first-out (LIFO) collection. You might use a Stack to track
changes so that the most recent change can be undone.
StringCollection Like ArrayList, except values are strongly typed as strings, and
StringCollection does not support sorting.
BitArray A collection of boolean values.

ArrayList

Use the ArrayList class (in the System.Collections namespace) to add objects that can
be accessed directly using a zero-based index or accessed in a series using a foreach
loop. The capacity of an ArrayList expands as required. The following example shows
how to use the ArrayList.Add method to add different types of objects to a single array,
and then access each object using a foreach loop:

' VB
Dim al As New ArrayList()
al.Add("Hello")
al.Add("World")
al.Add(5)
al.Add(New FileStream("delemete", FileMode.Create))
Console.WriteLine("The array has " + al.Count.ToString + " items:")
For Each s As Object In al
Console.WriteLine(s.ToString())
Next

// C#
ArrayList al = new ArrayList();
al.Add("Hello");
al.Add("World");
al.Add(5);
al.Add(new FileStream("delemete", FileMode.Create));
Console.WriteLine("The array has " + al.Count + " items:");
foreach (object s in al)
Console.WriteLine(s.ToString());

This console application displays the following:

The array has 4 items:
Hello
World
5
System.IO.FileStream

In practice, you generally add items of a single type to an ArrayList. This allows you to
call the Sort method to sort the objects using their IComparable implementation. You
can also use the Remove method to remove an object you previously added and use
the Insert method to add an element at the specified location in the zero-based index.
The following code sample demonstrates this:

' VB
Dim al As New ArrayList()
al.Add("Hello")
al.Add("World")
al.Add("this")
al.Add("is")
al.Add("a")
al.Add("test")
al.Remove("test")
al.Insert(4, "not")
al.Sort()
For Each s As Object In al
Console.WriteLine(s.ToString())
Next

// C#
ArrayList al = new ArrayList();
al.Add("Hello");
al.Add("World");
al.Add("this");
al.Add("is");
al.Add("a");
al.Add("test");
al.Remove("test");
al.Insert(4, "not");
al.Sort();
foreach (object s in al)
Console.WriteLine(s.ToString());

This code sample results in the following display. Notice that the items are sorted
alphabetically (using the string IComparable implementation) and “test” has been
removed:

A
Hello
is
not
this
World

Queue and Stack

The Queue and Stack classes (in the System.Collections namespace) store objects that
can be retrieved and removed in a single step. Queue uses a FIFO sequence, while
Stack uses a LIFO sequence. The Queue class uses the Enqueue and Dequeue methods
to add and remove objects, while the Stack class uses Push and Pop. The following
code demonstrates the differences between the two classes:

' VB
Dim q As New Queue()
q.Enqueue("Hello")
q.Enqueue("world")
q.Enqueue("just testing")
Console.WriteLine("Queue demonstration:")
For i As Integer = 1 To 3
Console.WriteLine(q.Dequeue().ToString())
Next
Dim s As New Stack()
s.Push("Hello")
s.Push("world")
s.Push("just testing")
Console.WriteLine("Stack demonstration:")
For i As Integer = 1 To 3
Console.WriteLine(s.Pop().ToString())
Next

// C#
Queue q = new Queue();
q.Enqueue("Hello");
q.Enqueue("world");
q.Enqueue("just testing");
Console.WriteLine("Queue demonstration:");
for (int i = 1; i <= 3; i++)
Console.WriteLine(q.Dequeue().ToString());
Stack s = new Stack();
s.Push("Hello");
s.Push("world");
s.Push("just testing");
Console.WriteLine("Stack demonstration:");
for (int i = 1; i <= 3; i++)
Console.WriteLine(s.Pop().ToString());

The application produces the following output:

Queue demonstration:
Hello
world
just testing
Stack demonstration:
just testing
world
Hello

You can also use Queue.Peek and Stack.Peek to access an object without removing it
from the stack. Use Queue.Clear and Stack.Clear to remove all objects from the stack.

BitArray and BitVector32
BitArray is an array of boolean values, where each item in the array is either true or
false. While BitArray can grow to any size, BitVector32 (a structure) is limited to exactly
32 bits. If you need to store boolean values, use BitVector32 anytime you require 32 or
fewer items, and use BitArray for anything larger.

Dictionaries
Dictionaries map keys to values. For example, you might map an employee ID
number to the object that represents the employee, or you might map a product ID to
the object that represents the product. The .NET Framework includes the following
dictionary classes:
Hashtable A dictionary of name/value pairs that can be retrieved by name or
index
 SortedList A dictionary that is sorted automatically by the key
 StringDictionary A hashtable with name/value pairs implemented as strongly
typed strings
 ListDictionary A dictionary optimized for a small list of objects with fewer than
10 items
HybridDictionary A dictionary that uses a ListDictionary for storage when the
number of items is small and automatically switches to a Hashtable as the list grows
 NameValueCollection A dictionary of name/value pairs of strings that allows
retrieval by name or index

SortedList (in the System.Collections namespace) is a dictionary that consists of key/
value pairs. Both the key and the value can be any object. SortedList is sorted automatically
by the key. For example, the following code sample creates a SortedList instance
with three key/value pairs. It then displays the definitions for Queue, SortedList, and
Stack, in that order:

' VB
Dim sl As New SortedList()
sl.Add("Stack", "Represents a LIFO collection of objects.")
sl.Add("Queue", "Represents a FIFO collection of objects.")
sl.Add("SortedList", "Represents a collection of key/value pairs.")
For Each de As DictionaryEntry In sl
Console.WriteLine(de.Value)
Next

// C#
SortedList sl = new SortedList();
sl.Add("Stack", "Represents a LIFO collection of objects.");
sl.Add("Queue", "Represents a FIFO collection of objects.");
sl.Add("SortedList", "Represents a collection of key/value pairs.");
foreach (DictionaryEntry de in sl)
Console.WriteLine(de.Value);

Notice that SortedList is an array of DictionaryEntry objects. As the previous code sample
demonstrates, you can access the objects you originally added to the SortedList
using the DictionaryEntry.Value property. You can access the key using the Dictionary-
Entry.Key property.

You can also access values directly by accessing the SortedList as a collection. The following
code sample (which builds upon the previous code sample) displays the
definition for Queue twice. Queue is the first entry in the zero-based index because the
SortedList instance automatically sorted the keys alphabetically:

' VB
Console.WriteLine(sl("Queue"))
Console.WriteLine(sl.GetByIndex(0))
// C#
Console.WriteLine(sl["Queue"]);
Console.WriteLine(sl.GetByIndex(0));

The ListDictionary class (in the System.Collections.Specialized namespace) also
provides similar functionality, and is optimized to perform best with lists of fewer
than 10 items. HybridDictionary (also in the System.Collections.Specialized namespace)
provides the same performance as ListDictionary with small lists, but it scales better
when the list is expanded.
While SortedList can take an object of any type as its value (but only strings as keys),
the StringDictionary class (in the System.Collections.Specialized namespace) provides
similar functionality, without the automatic sorting, and requires both the keys and
the values to be strings.
NameValueCollection also provides similar functionality, but it allows you to use either
a string or an integer index for the key. In addition, you can store multiple string values
for a single key. The following code sample demonstrates this by displaying two
definitions for the terms stack and queue:

' VB
Dim sl As New NameValueCollection()
sl.Add("Stack", "Represents a LIFO collection of objects.")
sl.Add("Stack", "A pile of pancakes.")
sl.Add("Queue", "Represents a FIFO collection of objects.")
sl.Add("Queue", "In England, a line.")
sl.Add("SortedList", "Represents a collection of key/value pairs.")
For Each s As String In sl.GetValues(0)
Console.WriteLine(s)
Next
For Each s As String In sl.GetValues("Queue")
Console.WriteLine(s)
Next

// C#
NameValueCollection sl = new NameValueCollection();
sl.Add("Stack", "Represents a LIFO collection of objects.");
sl.Add("Stack", "A pile of pancakes.");
sl.Add("Queue", "Represents a FIFO collection of objects.");
sl.Add("Queue", "In England, a line.");
sl.Add("SortedList", "Represents a collection of key/value pairs.");
foreach (string s in sl.GetValues(0))
Console.WriteLine(s);
foreach (string s in sl.GetValues("Queue"))
Console.WriteLine(s);

Generic Collections :
Collections like ArrayList, Queue, and Stack use the Object base class to allow them to
work with any type. However, accessing the collection usually requires you to cast
from the base Object type to the correct type. Not only does this make development
tedious and more error-prone, but it hurts performance

Using generics, you can create strongly typed collections for any class, including custom
classes. This simplifies development within the Visual Studio editor, helps ensure
appropriate use of types, and can improve performance by reducing the need to cast
Generics Overview
Many of the collections in the .NET Framework support adding objects of any type,
such as ArrayList. Others, like StringCollection, are strongly typed. Strongly typed
classes are easier to develop with because the Visual Studio designer can list and
validate members automatically. In addition, you do not need to cast classes to more
specific types, and you are protected from casting to an inappropriate type.
Generics provide many of the benefits of strongly typed collections, but they can work
with any type that meets the requirements. In addition, using generics can improve performance
by reducing the number of casting operations required. Table 4-1 lists the most
useful generic collection classes and the corresponding nongeneric collection type.

Generic Collection Classes
List<T> ArrayList, StringCollection
Dictionary<T,U> Hashtable, ListDictionary, HybridDictionary,OrderedDictionary, NameValueCollection, StringDictionary
Queue<T> Queue
Stack<T> Stack
SortedList<T,U> SortedList
Collection<T> CollectionBase
ReadOnlyCollection<T> ReadOnlyCollectionBase

Generic SortedList<T,U> Collection
The following code sample creates a generic SortedList<T,U> using strings as the keys
and integers as the values. As you type this code into the Visual Studio editor, notice
that it prompts you to enter string and integer parameters for the SortedList.Add
method as if SortedList.Add were strongly typed:
' VB
Dim sl As New SortedList(Of String, Integer)()
sl.Add("One", 1)
sl.Add("Two", 2)
sl.Add("Three", 3)
For Each i As Integer In sl.Values
Console.WriteLine(i.ToString())
Next

// C#
SortedList<string, int> sl = new SortedList<string,int>();
sl.Add("One", 1);
sl.Add("Two", 2);
sl.Add("Three", 3);
foreach (int i in sl.Values)
Console.WriteLine(i.ToString());

In Visual Basic, specify the type arguments for the generic class using the constructor
parameters by specifying the Of keyword. In C#, specify the type arguments using
angle brackets before the constructor parameters.

Using Generics with Custom Classes

You can use generics with custom classes as well. Consider the following class declaration:
' VB
Public Class person
Private firstName As String
Private lastName As String
Public Sub New(ByVal _firstName As String, ByVal _lastName As String)
firstName = _firstName
lastName = _lastName
End Sub
Public Overloads Overrides Function ToString() As String
Return firstName + " " + lastName
End Function
End Class

// C#
public class person
{
string firstName;
string lastName;
public person(string _firstName, string _lastName)
{
firstName = _firstName;
lastName = _lastName;
}
override public string ToString()
{
return firstName + " " + lastName;
}
}

You can use the SortedList<T,U> generic class with the custom class exactly as you
would use it with an integer, as the following code sample demonstrates:
' VB
Dim sl As New SortedList(Of String, person)()
sl.Add("One", New person("Mark", "Hanson"))
sl.Add("Two", New person("Kim", "Akers"))
sl.Add("Three", New person("Zsolt", "Ambrus"))
For Each p As person In sl.Values
Console.WriteLine(p.ToString())
Next

// C#
SortedList<string, person> sl = new SortedList<string,person>();
sl.Add("One", new person("Mark", "Hanson"));
sl.Add("Two", new person("Kim", "Akers"));
sl.Add("Three", new person("Zsolt", "Ambrus"));
foreach (person p in sl.Values)
Console.WriteLine(p.ToString());
Generic Queue<T> and Stack<T> Collections
Similarly, the following code sample demonstrates using the generic versions of both
Queue and Stack with the person class:

' VB
Dim q As New Queue(Of person)()
q.Enqueue(New person("Mark", "Hanson"))
q.Enqueue(New person("Kim", "Akers"))
q.Enqueue(New person("Zsolt", "Ambrus"))
Console.WriteLine("Queue demonstration:")
For i As Integer = 1 To 3
Console.WriteLine(q.Dequeue().ToString())
Next
Dim s As New Stack(Of person)()
s.Push(New person("Mark", "Hanson"))
s.Push(New person("Kim", "Akers"))
s.Push(New person("Zsolt", "Ambrus"))
Console.WriteLine("Stack demonstration:")
For i As Integer = 1 To 3
Console.WriteLine(s.Pop().ToString())
Next

// C#
Queue<person> q = new Queue<person>();
q.Enqueue(new person("Mark", "Hanson"));
q.Enqueue(new person("Kim", "Akers"));
q.Enqueue(new person("Zsolt", "Ambrus"));
Console.WriteLine("Queue demonstration:");
for (int i = 1; i <= 3; i++)
Console.WriteLine(q.Dequeue().ToString());
Stack<person> s = new Stack<person>();
s.Push(new person("Mark", "Hanson"));
s.Push(new person("Kim", "Akers"));
s.Push(new person("Zsolt", "Ambrus"));
Console.WriteLine("Stack demonstration:");
for (int i = 1; i <= 3; i++)
Console.WriteLine(s.Pop().ToString());

Generic List<T> Collection
Some aspects of generic collections might require specific interfaces to be implemented
by the type you specify. For example, calling List.Sort without any parameters
requires the type to support the IComparable interface. The following code sample
expands the person class to support the IComparable interface and the required
CompareTo method and allows it to be sorted in a List<T> generic collection using the
person’s first and last name:
' VB
Public Class person
Implements IComparable
Private firstName As String
Private lastName As String
Public Function CompareTo(ByVal obj As Object) _
As Integer Implements System.IComparable.CompareTo
Dim otherPerson As person = DirectCast(obj, person)
If Me.lastName <> otherPerson.lastName Then
Return Me.lastName.CompareTo(otherPerson.lastName)
Else
Return Me.firstName.CompareTo(otherPerson.firstName)
End If
End Function
Public Sub New(ByVal _firstName As String, ByVal _lastName As String)
firstName = _firstName
lastName = _lastName
End Sub
Public Overrides Function ToString() As String
Return firstName + " " + lastName
End Function
End Class

// C#
public class person : IComparable
{
string firstName;
string lastName;
public int CompareTo(object obj)
{
person otherPerson = (person)obj;
if (this.lastName != otherPerson.lastName)
return this.lastName.CompareTo(otherPerson.lastName);
else
return this.firstName.CompareTo(otherPerson.firstName);
}
public person(string _firstName, string _lastName)
{
firstName = _firstName;
lastName = _lastName;
}
override public string ToString()
{
return firstName + " " + lastName;
}
}
After adding the IComparable interface to the person class, you now can sort it in a
generic List<T>, as the following code sample demonstrates:
' VB
Dim l As New List(Of person)()
l.Add(New person("Mark", "Hanson"))
l.Add(New person("Kim", "Akers"))
l.Add(New person("Zsolt", "Ambrus"))
l.Sort()
For Each p As person In l
Console.WriteLine(p.ToString())
Next
// C#
List<person> l = new List<person>();
l.Add(new person("Mark", "Hanson"));
l.Add(new person("Kim", "Akers"));
l.Add(new person("Zsolt", "Ambrus"));
l.Sort();
foreach (person p in l)
Console.WriteLine(p.ToString());

With the IComparable interface implemented, you could also use the person class as
the key in a generic SortedList<T,U> or SortedDictionary<T,U> class.

No comments:

Post a Comment