In one of my first posts I briefly mentioned that I would discuss a bit about what I've learned about using the new generic collections.  I've been putting it off a bit because I knew it would be a bit lengthy, but I really feel this is important so I'm going to push myself to start making the effort. ;)  Here's what I plan to cover (in relation to generic collections):

  • Advantages of generic collections
  • Predicates & other System delegate types
  • Anonymous delegates
  • Subclassing generic collections
  • Other resources (including Power Collections)

I'm not going to go over generics.  If you want details on generics here are a few links:

I also won't go over anonymous delegates in detail.  You can find some useful information here:

 

Introduction

Generic collections are the generic equivalents (I like to think of them as replacements) for the System.Collections classes.  They are found in the System.Collections.Generic space.

Generic collections have a number of advantages over the old collections.  Chief among them are:

  • They are type safe.
  • Better performance.  (no boxing/unboxing!)
  • Richer functionality through System defined delegate types.

To top it off they are quite compatible with the old collections and methods that use them.  Both ArrayList and List<T>  support IList, ICollection, IEnumerable, and ICloneable for example.  If existing methods use the old interfaces for parameters there is nothing extra you have to do to start using the generic collections.   (Hey, look! A great reason to take the interface (IList) as a parameter instead of the collection class (ArrayList).)  Converting is easy beyond this as the old collections take ICollection or IDictionary as a constructor parameter.

Here's a quick overview of how the new collections map to old ones (from: Generics in the .NET Framework Class Library (C# Programmers Reference))

Generic Class or Interface

Description

Corresponding Non-Generic Type

Collection<T>

ICollection<T>

Provides the base class for a generic collection.

CollectionBase

ICollection

Comparer<T>

IComparer<T>

IComparable<T>

Compares two objects of the same generic type for equivalence and for sorting.

Comparer

IComparer

IComparable

Dictionary<K, V>

IDictionary<K,V>

Represents a collection of key/value pairs that are organized based on the key.

Hashtable

IDictionary

Dictionary<K, V>.KeyCollection

Represents the collection of keys in a Dictionary<K, V>.

None.

Dictionary<K, V>.ValueCollection

Represents the collection of values in a Dictionary<K, V>.

None.

IEnumerable<T>

IEnumerator<T>

Represents a collection that can be iterated using foreach.

IEnumerable

IEnumerator

KeyedCollection<T, U>

Represents a keyed collection.

KeyedCollection

LinkedList<T>

Represents a doubly linked list.

None.

LinkedListNode<T>

Represents a node in a LinkedList<T>.

None.

List<T>

IList<T>

Implements the IList<T> interface using an array whose size is dynamically increased as required.

ArrayList

IList

Queue<T>

Represents a first-in, first-out collection of objects.

Queue

ReadOnlyCollection<T>

Provides the base class for a generic read-only collection.

ReadOnlyCollectionBase

SortedList<K, V>

Represents a collection of key/value pairs that are sorted by key based on the associated IComparer<T> implementation.

SortedList

Stack<T>

Represents a simple last-in-first-out (LIFO) collection of objects.

Stack

 

First Look at a Generic Collection Class

The best place to start is to look at the workhorse of the generic collection family, the generic List class, or List<T>.  List<T> is meant to replace the functionality of ArrayList.  It is a simple ordered collection of objects of type <T>.  You can think of it as a wrapped array because that's precisely what it is--internally data is stored as private T[] _items;

As with any array, appending is usually fast (unless the array is being resized) and insertion gets proportionally slower as the number of elements grows.  I've already mentioned that it supports IList, ICollection, and IEnumerable.  It also supports the generic equivalents IList<T>, ICollection<T>, and IEnumerable<T>.

Lets take a look at some simple usage of List<T>, shall we?  (Finally, some code!)

// Creating a List<T> (Also has constructors that take a collection or specify the initial size)
List<int> myNumbers = new List<int>();
// Adding a value myNumbers.Add(42);
// Adding a range of values myNumbers.AddRange(new int[]{1,2,3});
// Inserting a value myNumbers.Insert(myNumbers.Count - 3, 0);
// Iterating foreach (int number in myNumbers) {    // Output is 42, 0, 1, 2, 3    Console.WriteLine(number.ToString()); }

The code above should be pretty self explanatory.  Here is a summary of the functionality that is available to you in List<T>:

  • Add or insert objects of collections of type T
  • Removal
    • Remove, RemoveAt, RemoveRange, RemoveAll
  • Sorting
    • Sort (Comparison<T>)
    • Reverse
  • Conversion
    • ConvertAll (Converter<T,U>)
    • CopyTo(T[])
  • Searching (Predicate<T>)
    • Exists
    • Find, FindAll, FindLast
    • FindIndex, FindLastIndex, IndexOf, LastIndexOf
    • TrueForAll
  • Iterating
    • ForEach (Action<T>)

In bold you will see that I've called out the System delegate types.  They are the key to unlocking super rich functionality within these classes.  This makes a nice transition to my next blog entry where I'll start by taking a look at Predicate<T> and then this discussion will get really interesting. :)