Collection Classes in C#

1. Synchronized Collections

When a collection class—such as ArrayList—is created, it isn’t thread-safe, because adding synchronization to such a class imposes some overhead. If you need a thread-safe version, simply call the Synchronized() method to get a thread-safe wrapper to the list.

In other words, you can use the following to create a thread-safe ArrayList:

ArrayList arr = ArrayList.Synchronized(new ArrayList());

For more information on threading and synchronization, see Chapter 31.

The generics collections don’t support the static Synchronized method, and you should use standard synchronization techniques such as the lock statement; again, see Chapter 31.

2. Case-Insensitive Collections

To deal with strings in a case-insensitive manner, the .NET Framework provides a way to create case-insensitive versions of the SortedList and Hashtable collection classes. This support is supplied through the CollectionsUtil class in the System.Collections.Specialized namespace by calling the CreateCaseInsensitiveSortedList() or CreateCaseInsensitiveHashtable() function.

2.1. ICloneable

You can use the object.MemberWiseClone() function to create a clone of an object. The default implementation of this function produces a shallow copy of an object; the fields of an object are copied exactly rather than duplicated. Consider the following:

using System; class ContainedValue {

public ContainedValue(int count)

{

this.count = count;

}

public int count;

class MyObject

{

public MyObject(int count)

{

this.contained = new ContainedValue(count);

}

public MyObject Clone()

{

return((MyObject) MemberwiseClone());

}

public ContainedValue contained;

}

class Test {

public static void Main()

{

MyObject         my = new MyObject(33);

MyObject         myClone = my.Clone();

Console.WriteLine(  “Values: {0} {1}”,

my.contained.count,

myClone.contained.count);

myClone.contained.count = 15; Console.WriteLine( “Values: {0} {1}”,

my.contained.count,

myClone.contained.count);

}

}

This example produces the following output:

Values: 33 33

Values: 15 15

Because the copy made by MemberWiseClone() is a shallow copy, the value of contained is the same in both objects, and changing a value inside the ContainedValue object affects both instances of MyObject.

What’s needed is a deep copy, where a new instance of ContainedValue is created for the new instance of MyObject. You do this by implementing the ICloneable interface:

using System;

class ContainedValue {

public ContainedValue(int count)

{

this.count = count;

}

public int count;

}

class MyObject: ICloneable

{

public MyObject(int count)

{

this.contained = new ContainedValue(count);

}

public object Clone()

{

Console.WriteLine(“Clone”);

return(new MyObject(this.contained.count));

}

public ContainedValue contained;

}

class Test {

public static void Main()

{

MyObject         my = new MyObject(33);

MyObject         myClone = (MyObject) my.Clone();

Console.WriteLine(“Values: {0} {1}”,

my.contained.count,

myClone.contained.count);

myClone.contained.count = 15; Console.WriteLine(“Values: {0} {1}”,

my.contained.count,

myClone.contained.count);

}

}

This example produces the following output:

Values: 33 33

Values: 33 15

The call to MemberWiseClone() will now result in a new instance of ContainedValue, and you can modify the contents of this instance without affecting the contents of my.

Unlike some of the other interfaces that might be defined on an object, ICloneable isn’t called by the runtime; it’s provided merely to ensure that the Clone() function has the proper signature. Some objects may choose to implement a constructor that takes an instance as a parameter instead.

3. Other Collections

In addition to the collection classes that have already been discussed, the .NET Framework provides a number of others, as shown in Table 30-1 and Table 30-2.

4. Design Guidelines

You should consider the intended use of an object when deciding which virtual functions and interfaces to implement. Table 30-3 provides guidelines for this (generic eqivalents are shown in parentheses).

4.1. Functions and Interfaces by Framework Class

Tables 30-4, 30-5, 30-6, and 30-7 summarize which functions or interfaces on an object are used by each collection class. The generic eqivalents are shown in parentheses.

4.2. Choosing Generics vs. Nongeneric Collections

You should use generic collections as often as possible, as opposed to using nongeneric collections. The generic collections are much faster (up to an order of magnitude) when working with value types and are still significantly faster when working with reference types. The generic collections also provide compile-time type-safety that isn’t available with the nongeneric collections; further, the designers incorporated what worked best in version 1 of the .NET Framework libraries into the design and implementation of generic collections.

Generics are CLS-compliant in .NET 2.0, which means the range of languages that will be able to consume, extend, and produce generic classes will be substantial, so shying away from generic collections in the interests of language interoperability is unwarranted.

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *