BlackWasp
.NET Framework
.NET 1.1

Collection Interfaces

The thirty-sixth part of the C# Fundamentals tutorial introduces the use of collections in C#.  A collection is similar to an array in that it contains a group of objects.  However, the use of varying types of collection provide for more functionality.

What is a Collection?

A collection in C# is a group of related objects held in a structure.  There are many types of collection defined within the .NET framework, each providing a different structure of objects.  These include collections for holding simple lists, queues and stacks.

A collection is often compared to an array.  Both provide structures that are capable of storing a group of related objects.  However, there are some key differences.  For example, collections are one-dimensional unlike arrays, which may have many dimensions.  Arrays tend to be fixed structures with a pre-defined size whereas collections can be variable length and can have items inserted at or removed from any position within the set.

One of the most important benefits provided by the standard collection types is that they remove the need to develop common structures and algorithms.  These include the already mentioned queue and stack, sorted lists, bit arrays and associative arrays, also known as dictionaries or collections of key-value pairs.

Boxing and Unboxing

Collections can only be used to hold groups of objects.  This means that value types and structures cannot be natively held in a collection.  However, it is possible to hold a value or structure in an object using the concept of boxing.  When a value is assigned to an object, the value is boxed and behaves like a reference type object.  The object can later be converted back to a value, or unboxed, by casting it to its original data type.

The following example demonstrates a value being boxed and then unboxed.

int valueToBox = 123;
object boxed = valueToBox;                      // Boxed
int valueOutOfBox = (int)boxed;                 // Unboxed

Console.WriteLine(valueOutOfBox);               // Outputs "123"

NB: When adding value types and structures to a collection, boxing occurs automatically without the need to manually convert the value into an object.  When retrieving a value from a collection, it must be unboxed using the cast operator.

Collection Types

There are three main types of collection provided by the .NET framework:

  • General-Purpose.  The general-purpose collections are used to provide various standard data structures that hold any type of object.  These include the dynamically sized array, stack and queue.  The set of general-purpose collections also includes dictionaries that hold key-value pairs; each entry having both a unique key and a separate value object.
  • Bit-Based Collections.  The bit-based collections hold groups of binary digits only.  The functionality of these collections is specific to the handling of bit arrays, including setting and resetting of individual bits and performing bitwise logical operations.
  • Specialised (or Specialized)Specialised collections are provided for optimised processing of specific types of data or structure.  These collections include specialised versions of general-purpose collections and dictionaries.

Each of the above types of collection will be investigated in future articles in the C# Fundamentals tutorial.  It is also possible to create your own collections when the provided options do not meet your exact requirements.  However, this is a complex topic that is beyond the scope of this beginners' tutorial.

Collection Interfaces

All of the collections available to C# developers use a set of common interfaces.  The interface is an object-oriented programming concept that will be described fully in a future tutorial.  In simple terms, interfaces define a number of public properties and methods that must be provided by any class that implements the interface.  This is important as the shared interfaces define the basic functionality of each collection class.  This also means that one collection type can often be easily substituted for another in the source code of a program as their behaviours will be similar.

Each of the key interfaces is described in the following sections.  Note that not all of the collections implement every interface.

ICollection Interface

The first interface to consider is ICollection.  Every collection implements this interface and all of its properties and methods.  The properties and methods are described in the following sections with examples for the key items.

NB: All of the collection interfaces have an "I" prefix.  This is a standard naming convention for interfaces.

ICollection.CopyTo

The CopyTo method provides the facility to copy each item from a collection into a standard array.  The basic CopyTo method accepts two parameters.  The first parameter is the name of an array that has been created previously.  The second parameter is an integer representing the index of the first item in the array to copy to.  Each item from the collection is placed in subsequent indexes of the array.

In the example below, the collection is copied to index zero of the array, which is the first element.  The example demonstrates the use of CopyTo for the ArrayList, a commonly used collection that provides a one-dimensional dynamic array structure.  The ArrayList is found in the System.Collections namespace so to execute the example, add using System.Collections; to the source code.

ArrayList names = new ArrayList();

// Add some names
names.Add("Andrew");
names.Add("Alex");
names.Add("Adrienne");

// Copy the collection into a new array of strings
string[] namesArray = new string[3];
names.CopyTo(namesArray, 0);

foreach(string name in namesArray)
    Console.WriteLine(name);

/* OUTPUT

Andrew
Alex
Adrienne

*/

NB: Strings are used throughout this article within the examples.  These are used for ease of demonstration only and could be replaced with any type of object.

ICollection.Count

The Count property is used to count the number of items that exist within a collection.  The property returns an integer value.

ArrayList names = new ArrayList();

// Add some names
names.Add("Andrew");
names.Add("Alex");
names.Add("Adrienne");

Console.WriteLine(names.Count);                 // Outputs "3"

ICollection.IsSynchronized

When writing programs that are multi-threaded, it is possible that more than one thread of execution could access a collection at the same time.  If many threads are reading from and writing to the collection simultaneously, unexpected problems could occur.  To alleviate this problem collections may be synchronised.  A synchronised (or synchronized) collection allows writing to the collection from multiple threads.

To determine if a collection is synchronised, the IsSynchronized property can be read.  This returns a Boolean value indicating if the collection is synchronised or not.  By default, collections are not synchronised.

ArrayList names = new ArrayList();

Console.WriteLine(names.IsSynchronized);        // Outputs "False"

ICollection.SyncRoot

The SyncRoot property returns an object that may be used for synchronisation for thread-safety.  Even when a collection is synchronised there are situations where it is not thread-safe.  For example, when looping, or enumerating, through a collection's items another thread may modify the collection.  This causes an exception to be thrown.  To guarantee thread-safety in this situation, the exceptions can be caught and handled or the collection may be locked using the SyncRoot object.  Whilst locked, no other threads may access the collection.

The following example demonstrated how a collection may be locked.  The collection remains unavailable to other threads until the end of the code block associated with the lock command.

ArrayList names = new ArrayList();

// Add some names
names.Add("Andrew");
names.Add("Alex");
names.Add("Adrienne");

lock (names.SyncRoot)                           // Lock the collection
{
    foreach(string name in names)
        Console.WriteLine(name);
}                                               // Collection now unlocked

/* OUTPUT

Andrew
Alex
Adrienne

*/

IList Interface

The IList interface declares the properties and methods, or behaviour, of a collection that represents a list of numbered items.  Each item in a list has a zero-based index number that is similar to an array's index.  This is not coincidental as all arrays have this behaviour because they also implement the IList interface.

The properties and methods of the IList interface are described below.  Note that not all collections support every behaviour.  Where a method or property is not supported, the function throws a NotSupportedException.  The IList interface is derived from, or inherits, ICollection.  This means that all collections that support the IList behaviour also implement the ICollection properties and methods.

IList.Add

The Add method adds a new object to the collection; the object to add being passed as the only parameter.  The method returns an integer value indicating the index position at which the new object was inserted.  Remember that the index is zero-based so the first entry is at position zero.

ArrayList names = new ArrayList();

// Add some names
names.Add("Andrew");
names.Add("Alex");
int position = names.Add("Adrienne");

Console.WriteLine(position);                    // Outputs "2"

IList.Remove

The Remove method removes an object from a collection, using the object itself as a lookup parameter.  If the object has multiple occurrences within the list, only the first match is removed.  If the object does not exist within the collection then the operation has no effect.

ArrayList names = new ArrayList();

// Add some names
names.Add("Andrew");
names.Add("Alex");
names.Add("Adrienne");
names.Remove("Andrew");

Console.WriteLine(names.Count);                 // Outputs "2"

IList.Insert

The Insert method is similar to the Add method as it allows new items to be added to the collection.  However, the Insert method allows the index of the new item to be specified.  The object is inserted at the specified index with existing objects being moved to a higher index to make space.  If the index number specified is too large, an ArgumentOutOfRangeException is thrown.

ArrayList names = new ArrayList();

// Add some names
names.Add("Andrew");
names.Add("Alex");
names.Insert(0, "Adrienne");                    // Insert at start of collection

foreach(string name in names)
    Console.WriteLine(name);

/* OUTPUT

Adrienne
Andrew
Alex

*/

IList.RemoveAt

The RemoveAt method removes an item from the collection using the index number to identify the object to delete.  Any items in the collection with a higher index number are moved to ensure that the index numbers remain contiguous.

ArrayList names = new ArrayList();

// Add some names
names.Add("Andrew");
names.Add("Alex");
names.Add("Adrienne");
names.RemoveAt(1);                              // Remove the second item

foreach(string name in names)
    Console.WriteLine(name);

/* OUTPUT

Andrew
Adrienne

*/

IList.Clear

The Clear method is the last IList method used to modify the contents of a collection.  This method simply deletes all of the items from the collection.

ArrayList names = new ArrayList();

// Add some names
names.Add("Andrew");
names.Add("Alex");
names.Add("Adrienne");
names.Clear();

Console.WriteLine(names.Count);                 // Outputs "0"

IList.Contains

Sometimes it is necessary to determine if an object exists within a collection.  The Contains method can be used for this purpose by passing the object to find as the only parameter.  The method returns a Boolean value indicated if the item is present or not.

ArrayList names = new ArrayList();

// Add some names
names.Add("Andrew");
names.Add("Alex");
names.Add("Adrienne");

Console.WriteLine(names.Contains("Alex"));      // Outputs "True"
Console.WriteLine(names.Contains("Albert"));    // Outputs "False"

IList.IndexOf

The final method of the IList interface is IndexOf.  This method is similar to the Contains method as it is used to determine if an object is present within a collection.  If the object exists, the index number of the item in the collection is returned.  If the object does not exist, the return value is -1.

ArrayList names = new ArrayList();

// Add some names
names.Add("Andrew");
names.Add("Alex");
names.Add("Adrienne");

Console.WriteLine(names.IndexOf("Alex"));       // Outputs "1"
Console.WriteLine(names.IndexOf("Brian"));      // Outputs "-1"

IList.IsFixedSize

Although most collections provide a dynamic group of items, it is possible to have a collection that is fixed in size.  A fixed size collection may be modified but new items may not be added and existing items may not be removed.  If either operation is attempted, a NotSupportedException is thrown.  To check if a collection is fixed in size, the Boolean IsFixedSize property is read.

ArrayList names = new ArrayList();

Console.WriteLine(names.IsFixedSize);           // Outputs "False"

IList.IsReadOnly

One step further than having a fixed size collection is a read-only collection.  Such a list may not be modified in any way.  To check if a collection is read-only, the Boolean IsReadOnly property is read.

ArrayList names = new ArrayList();

Console.WriteLine(names.IsReadOnly);            // Outputs "False"

NB: Fixed size and read-only collections are simply wrappers for existing collections that restrict write access.  If the underlying collection is modified then the restricted version mirrors the changes without throwing an exception.

IList.Item

The Item property provides direct access to any object within a collection using an index number.  The object at the stated position can be read or modified, unless this is prevented because the collection is read-only.  To specify the index number square brackets are used as with arrays.

The following example creates and populates an ArrayList using the Add method.  The value of one of the items in the collection is then read, modified and read again the object's index number.

ArrayList names = new ArrayList();

// Add some names
names.Add("Andrew");
names.Add("Alex");
names.Add("Adrienne");

Console.WriteLine(names[1]);
names[1] = "Alan";
Console.WriteLine(names[1]);

/* OUTPUT

Alex
Alan

*/

NB: This property is called the indexer for the IList collection.

IDictionary Interface

A dictionary is a special type of collection that supports the grouping together of related key-value pairs.  Each entry in the collection consists of two objects.  The first, called the key, is a unique object that is used to identify the entry in the collection.  The second is the value object.  The value does not have to be unique within the dictionary.

The IDictionary interface is implemented by all of the .NET dictionary collections.  As with IList, IDictionary inherits ICollection so also supports all of the ICollection behaviour.

IDictionary.Add

The Add method in IDictionary is similar to the IList.Add method.  It is used to add a new item to the dictionary.  As dictionaries are identified by the specified key rather than an index number, no value is returned.

The following examples of IDictionary behaviours use the Hashtable class.  This type of dictionary provides a fast method for retrieving items from the collection by hashing the key into a unique hash code value that is used as an index.

Hashtable users = new Hashtable();

// Add some users
users.Add("andyb", "Andrew Brown");
users.Add("alexg", "Alex Green");
users.Add("adrienneb", "Adrienne Black");

Console.WriteLine(users.Count);                 // Outputs "3"

IDictionary.Remove

The Remove method removes an object from a dictionary, using the unique key of the item to delete as the only parameter.  If the key does not exist within the collection then the operation has no effect.  It is not possible to remove an item from the collection using the value object with standard IDictionary functions.

Hashtable users = new Hashtable();

// Add some users
users.Add("andyb", "Andrew Brown");
users.Add("alexg", "Alex Green");
users.Add("adrienneb", "Adrienne Black");
users.Remove("andyb");

Console.WriteLine(users.Count);                 // Outputs "2"

IDictionary.Clear

The Clear method simply deletes all items from a dictionary.  This method provides similar functionality to the Clear method of the IList interface.

IDictionary.Contains

The Contains method provides similar functionality to IList.Contains.  The method returns a Boolean value indicating if a specific key exists within a dictionary.  No method is provided in IDictionary to check if a collection contains a specific value object.

Hashtable users = new Hashtable();

// Add some users
users.Add("andyb", "Andrew Brown");
users.Add("alexg", "Alex Green");
users.Add("adrienneb", "Adrienne Black");

Console.WriteLine(users.Contains("alexg"));       // Outputs "True"
Console.WriteLine(users.Contains("Alex Green"));  // Outputs "False"

IDictionary.IsFixedSize and IDictionary.IsReadOnly

In the description of the properties of the IList interface it was explained that collections can be of fixed size or can be read-only.  This is also true of dictionary collections so IDictionary provides the IsFixedSize and IsReadOnly properties;  both return a Boolean value indicating the current state of the collection.

IDictionary.Item

The IDictionary interface defines an Item property as the indexer for dictionary classes.  This allows individual items within the collection to be identified using an index provided in square brackets.  In the case of dictionaries the key object is used rather than an index number.

Hashtable users = new Hashtable();

// Add some users
users.Add("andyb", "Andrew Brown");
users.Add("alexg", "Alex Green");
users.Add("adrienneb", "Adrienne Black");

Console.WriteLine(users["alexg"]);              // Outputs "Alex Green"

IDictionary.Keys and IDictionary.Values

Dictionaries can be thought of as two related collections; one collection of keys and one collection of values.  This concept can be realised by extracting either group as a collection in its own right.  The Keys and Values properties provide access to these two sets of values, returning either as a collection.

The type of collection returned is not defined and may vary according to the type of dictionary being interrogated.  However, the returned collections will always inherit the ICollection interface.  As such, the return value can be declared using the ICollection interface name with the underlying class being safely ignored.

The following example uses both of these properties to loop through each key and value independently.

Hashtable users = new Hashtable();

// Add some users
users.Add("andyb", "Andrew Brown");
users.Add("alexg", "Alex Green");
users.Add("adrienneb", "Adrienne Black");

// Extract the keys and values
ICollection keys = users.Keys;
ICollection values = users.Values;

// Loop through each key and value
foreach(object key in keys)
    Console.WriteLine(key);
    
foreach(object value in values)
    Console.WriteLine(value);

/* OUTPUT

adrienneb
alexg
andyb
Adrienne Black
Alex Green
Andrew Brown

*/

Enumerators

In the above examples, the foreach command has been used to loop through all of the items in a collection.  This is possible because the ICollection interface and all of its derivatives support the IEnumerable interface.

The IEnumerable interface must be implemented by any class that allows looping with foreach.  It provides a single method, GetEnumerator, which returns an enumerator object.  An enumerator, also known as an iterator, contains the functionality required to traverse the items in a collection or array without requiring the developer to understand the precise implementation of the collection.

Link to this Page24 April 2007
RSS RSS Feed
79 users on-line