Writing Type-Safe Collections in C#
Pages: 1, 2
System.Collections.DictionaryBase
While CollectionBase allows us to implement strongly-typed collections of objects, another
class called
DictionaryBase
provides the abstract base class for creating
strongly-typed collections of (key, value) pairs. Just as CollectionBase encapsulates an
ArrayList, DictionaryBase encapsulates a Hashtable and provides access
to it via a protected property called InnerHashtable. DictionaryBase also contains
a protected property called Dictionary, which is nothing but DictionaryBase
itself returned as an IDictionary (notice that DictionaryBase implements
IDictionary). The semantics of defining explicit interface member implementations are the same
as in the previous case, which means that even though certain methods like Add and Remove
are defined in DictionaryBase, they can only be accessed via an instance of IDictionary.
The following is a type-safe hashtable that stores int/Customer pairs as opposed to
object/object pairs stored in a Hashtable. All calls to
CustomerTable are delegated to the Dictionary object--they could be delegated to the InnerHashtable object as well.
// CustomerTable.cs
public class CustomerTable : System.Collections.DictionaryBase
{
public Customer this[int customerID]
{
get { return ((Customer)(Dictionary[customerID])); }
set { Dictionary[customerID] = value; }
}
public void Add(int customerID, Customer customer)
{
Dictionary.Add(customerID, customer);
}
public void Remove(int customerID)
{
Dictionary.Remove(customerID);
}
public bool Contains(int customerID)
{
return Dictionary.Contains(customerID);
}
// Add other type-safe methods here
// ...
// ...
}
CollectionBase and DictionaryBase solve the problem more elegantly than the
first approach. They take most of the work out of implementing a type-safe collection. However, they
suffer from some drawbacks that we did not see in the first approach:
-
Since
CustomerListis not of typeArrayList, we cannot pass aCustomerListwherever anArrayListis expected. We could provide a method that returns theInnerListobject, but that would violate the rules of encapsulation. -
If we want type-safe versions of more specialized collections like
StackandQueue, we're out of luck using this approach.
Third Approach: Containing Existing Collection Classes
Even though the above approach works well for most collection classes, it does not address creation of
type-safe Stack and Queue classes. To implement these, we could either use the
inheritance method we saw in the first approach, or we could use the
containment/delegation method (similar to the second approach, except that this time we do it independent
of CollectionBase). The following class creates a type-safe Stack
of Customer objects using containment and delegation.
// CustomerStack.cs
public class CustomerStack
{
Stack stack;
public CustomerStack()
{
stack = new Stack();
}
public void Push(Customer customer)
{
stack.Push(customer);
}
public Customer Pop()
{
return ((Customer)(stack.Pop()));
}
public bool Contains(Customer customer)
{
return stack.Contains(customer);
}
public int Count
{
get { return stack.Count; }
}
public IEnumerator GetEnumerator()
{
return stack.GetEnumerator();
}
// Add other type-safe methods here
// ...
// ...
}
This approach gives us more flexibility in implementing our collections. For example, we could use the
same class to implement multiple collections. Also, containment protects us from any future changes
to the collections classes. The downside of this approach is the same as we saw with the second
approach--since CustomerStack is not of type Stack,
it cannot be passed wherever a Stack is expected.
In the above code, we could make CustomerStack implement the ICollection interface,
but in that case, we must be prepared to provide implementations for all of the methods and properties of
ICollection. By not implementing ICollection, we just chose a little freedom
for ourselves so that we could provide only the minimum functionality required for our stack implementation,
while still leveraging the contained Stack object by delegating all calls to it.
Conclusion
Even though all of the above approaches achieve compile-time type safety, none of them is particularly elegant or efficient, for the following reasons:
- All of them require us to write every type of collection for every type of object we wish to store. This sacrifices polymorphism and results in code duplication. The size of an application that wishes to achieve such precise type safety can increase rapidly, and fixing bugs in the duplicated sections can be quite a task.
- Since collection classes in C# are designed with reference types in mind, using them with value types leads to unnecessary boxing and unboxing, which degrades performance. All three approaches discussed in this article make use of the built-in collection classes, so they all suffer from the same performance problem.
The problems faced by the Collections Framework are precisely the problems that generic types try to solve, but until we see support for generics in C#, we will have to either implement our own type-safe collections, or succumb to writing type-unsafe code that rears its ugly head during a demo.
Amit Goel has been developing object-oriented applications for several years. You can learn more about him at www.amitgoel.com.
Return to ONDotnet.com
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 4 of 4.
-
.NET 2.0
2007-07-22 01:21:58 jefpatat [Reply | View]
With .NET 2.0 things got significantly simpler. You might also check these articles: http://jefspalace.be/cms/Generic+collections
-
Thanks!
2005-05-19 11:57:25 EvanStein [Reply | View]
Great article -- short, sweet, and after two years' time, the gift that keeps on giving.
Typed collections would be extremely handy if they were included in future versions of C#. While it's true that re-inventing the wheel can cause its own headaches, these workarounds can be fantastically helpful if used once or twice.
For instance, if a typed collection is designed for a class that's used all over an application, it promotes polymorphism and reuse. Equally important, the programmer can see the class members at design time without having to "box" and "unbox" everything by hand, or call unidiomatic functions.
Properties don't take indices in C#, and Microsoft recommends you write functions as a workaround -- kind of disappointing if you're a fan of elegance. So, it's up to you which workaround works best. If it doesn't grind things to a halt, there's a decided advantage in clear code! The clearest C# code is the code that looks like the rest of C#.
Though the author himself isn't sanguine about the whole thing, most of it is the worry about putting a loaded gun into the wrong hands. The rest, I suspect, is undue modesty! Thanks for the tip.
-
First Approach Override or Overload?
2003-04-04 05:19:29 mkh100 [Reply | View]
Examining the first approach in which CustomerArrayList is Inherited from Systems.Collections.ArrayList I feel I must be missing something.
The base class ArrayList for example exposes it's Add method as:
public virtual System.Int32 Add ( System.Object value )
By adding the suggested:
public int Add(Customer customer)
{
return base.Add(customer);
}
you are actually overloading rather than overriding the method because the signature of the method is different.
That is to say my CustomerArrayList can be called with a Customer as an argument (as we want), but I have done nothing to hide the base functionality, so I can still add any old object (derived from system.object) to the CustomerArrayList as well (which I don't want).
So in terms of a "strongly typed" ArrayList the first approach is really no improvement.
You could perhaps overide Add(Object obj) and perform some type checking there to make sure obj was of type customer, but this is a bit untidy. Methods 2 and 3 remain far superiour, but as Amit points out are quite typing intensive and have problems of their own.
-
type safe collections in c#
2003-03-18 04:02:08 gauthier-s [Reply | View]
If I'm right, it's a feature that will become usefull with the use of generics in the future version of c#.
look at this url:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dv_vstechart/html/vbconCProgrammingLanguageFutureFeatures.asp





