Your Ad Here

Monday, 14 December 2009

Lesson 20: Introduction to Generic Collections


All the way back in Lesson 02, you learned about arrays and how they allow you
to add and retrieve a collection of objects. Array's are good for many tasks,
but C# v2.0 introduced a new feature called generics. Among many benefits, one
huge benefit is that generics allow us to create collections that allow us to do
more than allowed by an array. This lesson will introduce you to generic
collections and how they can be used.  Here are the objectives for this lesson:



  • Understand how generic collections can benefit you
  • Learn how to create
    and use a generic List
  • Write code that implements a generic Dictionary


What Can Generics Do For Me?



Throughout this tutorial, you've learned about types, whether built-in (int,
float, char) or custom (Shape, Customer, Account). In .NET v1.0 there were
collections, such as the ArrayList for working with groups of objects. An
ArrayList is much like an array, except it could automatically grow and offered
many convenience methods that arrays don't have. The problem with ArrayList and
all the other .NET v1.0 collections is that they operate on type object. Since
all objects derive from the object type, you can assign anything to an
ArrayList. The problem with this is that you incur performance overhead
converting value type objects to and from the object type and a single ArrayList
could accidentally hold different types, which would cause a hard to find errors
at runtime because you wrote code to work with one type. Generic collections fix
these problems.



A generic collection is strongly typed (type safe), meaning that you can only
put one type of object into it.  This eliminates type mismatches at
runtime. Another benefit of type safety is that performance is better with value
type objects because they don't incur overhead of being converted to and from
type object. With generic collections, you have the best of all worlds because
they are strongly typed, like arrays, and you have the additional functionality,
like ArrayList and other non-generic collections, without the problems.



The next section will show you how to use a generic List collection.



Creating Generic List<T> Collections



The pattern for using a generic List collection is similar to arrays. You
declare the List, populate it's members, then access the members. Here's a code
example of how to use a List:


     List<int> myInts =         color="blue" family="Microsoft Sans Serif">new List<        color="blue" family="Microsoft Sans Serif">int>();

myInts.Add(1);
myInts.Add(2);
myInts.Add(3);

for ( family="Microsoft Sans Serif">int i = 0; i < myInts.Count; i++)
{
Console.WriteLine("MyInts: {0}", myInts[i]);
}


The first thing you should notice is the generic collection List<int>,
which is referred to as List of int. If you looked in the documentation for this
class, you would find that it is defined as List<T>, where T could
be any type.  For example, if you wanted the list to work on string
or Customer objects, you could define them as List<string> or
List<Customer>
and they would hold only string or Customer
objects. In the example above, myInts holds only type int.


Using the Add method, you can add as many int objects to the
collection as you want. This is different from arrays, which have a fixed size.
The List<T> class has many more methods you can use, such as Contains,
Remove, and more.


There are two parts of the for loop that you need to know about. First,
the condition uses the Count property of myInts. This is another
difference between collections and arrays in that an array uses a Length
property for the same thing. Next, the way to read from a specific position in
the List<T> collection, myInts[i], is the exact same syntax you
use with arrays.


The next time you start to use a single-dimension array, consider using a
List<T>
instead. That said, be sure to let your solution fit the problem and
use the best tool for the job.  i.e. it's common to work with byte[]
in many places in the .NET Framework.


Working with Dictionary<TKey, TValue> Collections



Another very useful generic collection is the Dictionary, which works
with key/value pairs. There is a non-generic collection, called a Hashtable
that does the same thing, except that it operates on type object.
However, as explained earlier in this lesson, you want to avoid the non-generic
collections and use thier generic counterparts instead. The scenario I'll use
for this example is that you have a list of Customers that you need to
work with. It would be natural to keep track of these Customers via their
CustomerID. The Dictionary example will work with instances of the
following Customer class:

            family="Microsoft Sans Serif"> public         family="Microsoft Sans Serif">class Customer
{
public Customer( color="blue" family="Microsoft Sans Serif">int id, color="blue" family="Microsoft Sans Serif">string name)
{
ID = id;
Name = name;
}

private color="blue" family="Microsoft Sans Serif">int m_id;

public color="blue" family="Microsoft Sans Serif">int ID
{
get { return m_id; }
set { m_id = value; }
}

private color="blue" family="Microsoft Sans Serif">string m_name;

public color="blue" family="Microsoft Sans Serif">string Name
{
get { return m_name; }
set { m_name = value; }
}
}



The Customer class above has a constructor to make it easier to
initialize. It also exposes it's state via public properties. It isn't very
sophisticated at this point, but that's okay because its only purpose is to help
you learn how to use a Dictionary collection.  The following example
populates a Dictionary collection with Customer objects and then
shows you how to extract entries from the Dictionary:

     Dictionary<        color="blue" family="Microsoft Sans Serif">int, Customer> customers =         color="blue" family="Microsoft Sans Serif">new Dictionary<        color="blue" family="Microsoft Sans Serif">int, Customer>();

Customer cust1 = new Customer(1, color="red" family="Microsoft Sans Serif">"Cust 1");
Customer cust2 = new Customer(2, color="red" family="Microsoft Sans Serif">"Cust 2");
Customer cust3 = new Customer(3, color="red" family="Microsoft Sans Serif">"Cust 3");

customers.Add(cust1.ID, cust1);
customers.Add(cust2.ID, cust2);
customers.Add(cust3.ID, cust3);

foreach (KeyValuePair< color="blue" family="Microsoft Sans Serif">int, Customer> custKeyVal color="blue" family="Microsoft Sans Serif">in customers)
{
Console.WriteLine(
"Customer ID: {0}, Name: {1}",
custKeyVal.Key,
custKeyVal.Value.Name);
}


The customers variable is declared as a Dictionary<int, Customer>
Considering that the formal declaration of Dictionary is
Dictionary<TKey, TValue>
, the meaning of customers is that it is a
Dictionary
where the key is type int and the value is type
Customer
. Therefore, any time you add an entry to the Dictionary, you
must provide the key because it is also the key that you will use to extract a
specified Customer from the Dictionary.


I created three Customer objects, giving each an ID and a Name.
I'll use the ID as the key and the entire Customer object as the
value. You can see this in the calls to Add, where custX.ID is
added as the key (first parameter) and the custX instance is added as the
value (second parameter).


Extracting information from a Dictionary is a little bit different.
Iterating through the customers Dictionary with a foreach
loop, the type returned is KeyValuePair<TKey, TValue>, where TKey
is type int and TValue is type Customer because those are
the types that the customers Dictionary is defined with.


Since custKeyVal is type KeyValuePair<int, Customer> it has Key
and Value properties for you to read from. In our example,
custKeyVal.Key
will hold the ID for the Customer instance and
custKeyVal.Value will hold the whole Customer instance. The
parameters in the Console.WriteLine statement demonstrate this by
printing out the ID, obtained through the Key property, and the
Name
, obtained through the Name property of the Customer
instance that is returned by the Value property.


The Dictionary type is handy for those situations where you need to keep
track of objects via some unique identifier. For your convenience, here's
Listing 20-1, shows how both the List and Dictionary collections
work.


Listing 20-1. Introduction to Using Generic Collections with an Example of the
List<T> and Dictionary<TKey, TValue> Generic Collections

using System;
using System.Collections.Generic;

public family="Microsoft Sans Serif">class Customer
{
public Customer( color="blue" family="Microsoft Sans Serif">int id, color="blue" family="Microsoft Sans Serif">string name)
{
ID = id;
Name = name;
}

private color="blue" family="Microsoft Sans Serif">int m_id;

public color="blue" family="Microsoft Sans Serif">int ID
{
get { return m_id; }
set { m_id = value; }
}

private color="blue" family="Microsoft Sans Serif">string m_name;

public color="blue" family="Microsoft Sans Serif">string Name
{
get { return m_name; }
set { m_name = value; }
}
}

class Program
{
static color="blue" family="Microsoft Sans Serif">void Main( color="blue" family="Microsoft Sans Serif">string[] args)
{
List<int> myInts = color="blue" family="Microsoft Sans Serif">new List< color="blue" family="Microsoft Sans Serif">int>();

myInts.Add(1);
myInts.Add(2);
myInts.Add(3);

for ( color="blue" family="Microsoft Sans Serif">int i = 0; i < myInts.Count; i++)
{
Console.WriteLine("MyInts: {0}", myInts[i]);
}

Dictionary<int, Customer> customers = color="blue" family="Microsoft Sans Serif">new Dictionary< color="blue" family="Microsoft Sans Serif">int, Customer>();

Customer cust1 = new Customer(1, color="red" family="Microsoft Sans Serif">"Cust 1");
Customer cust2 = new Customer(2, color="red" family="Microsoft Sans Serif">"Cust 2");
Customer cust3 = new Customer(3, color="red" family="Microsoft Sans Serif">"Cust 3");

customers.Add(cust1.ID, cust1);
customers.Add(cust2.ID, cust2);
customers.Add(cust3.ID, cust3);

foreach (KeyValuePair< color="blue" family="Microsoft Sans Serif">int, Customer> custKeyVal color="blue" family="Microsoft Sans Serif">in customers)
{
Console.WriteLine(
"Customer ID: {0}, Name: {1}",
custKeyVal.Key,
custKeyVal.Value.Name);
}

Console.ReadKey();
}
}


Whenever coding with the generic collections, add a using
System.Collections.Generic
declaration to your file, just as in Listing
20-1.


Summary



Generic collections give you the best of all worlds with the strong typing of
arrays and flexibility of non-generic collections. There are many more generic
collections to choose from also, such as Stack, Queue, and
SortedDictionary
.  Look in the System.Collections.Generic
namespace for other generic collections.


I invite you to return for Lesson 21: Anonymous Methods.


Your feedback and constructive contributions are welcome.  Please feel free
to contact me for feedback or comments you may have about this lesson.

0 Comments:

Your Ad Here