Strongly Typed Collections


The fact that most of the collections classes in the .NET Framework class library are designed to work with the System.Object type makes them very versatile. However, this flexibility can also be viewed as a minor problem, since it means you have to do a lot of casting (assuming you know the type in the first place). For example, assuming a Hashtable contains a number of items of the type Product , you have to cast each item you retrieve. Using C#, you would write:

  Hashtable products;   Product p;   p = (Product) product["ProductCode"];  

Using VB.NET, you would write:

  Dim products As Hashtable   Dim p As Product   p = CType(products("ProductCode"), Product)  

Casting types like this isn't difficult, but it can make code less readable, and often leads to silly compiler errors when you forget to cast. Strongly typed collections can resolve these problems. Rather than directly using the collection classes such as Hashtable in your class, you should instead provide a custom type that encapsulates the collection type being used, and also removes the need for casting.

For example, using C# you would write:

  ProductCollection products;   Product p;   p = products["ProductCode"];  

Using VB.NET, you would write:

  Dim products As ProductCollection   Dim p As Product   p = products("ProductCode")  

Implementing a strongly typed collection like this is relatively simple, and it allows you to build in additional rules and error handling, saving you from duplicating them throughout the code.

To a strongly typed collection, you have to:

  • Define the custom type of the item held in the collection.

  • Create the collection class, and implement the Add , Remove , and GetEnumerator methods , as well as the Item property.

Let's look at each of these in turn .

Defining the Custom Type

In the strongly typed example, a Product type was used. When implementing a collection, you should always have a custom type, and a collection for that custom type in which the collection name is simply the custom type name appended with Collection; for example, Product and ProductCollection , or Address and AddressCollection .

Here is a class definition for a Product type, written using VB.NET:

  Public Class Product     ' private fields   Private _code As string   Private _description As string   Private _price As Double     ' constructor   Public Sub New(initialCode As String, _   initialDescription As String, _   initialPrice As Double)   Code = initialCode   Description = initialDescription   Price = initialPrice   End Sub     Public Property Description As String   Get   Description = _description   End Get   Set   _description = Value   End Set   End Property     Public Property Code As String   Get   Code = _code   End Get   Set   _code = Value   End Set   End Property     Public Property Price As Double   Get   Price = _price   End Get   Set   _price = Value   End Set   End Property     End Class  

The Product type has three public properties:

  • Code : A unique code assigned to the product.

  • Description : A description of the product.

  • Price : The cost of the product.

All of the properties have a get and set accessor, and their implementation simply stores or retrieves the property value in a private field. The field name is the same as the property name, but prefixed with an underscore . The Product type has a constructor that accepts three parameters, allowing quick initialization. For example:

  Dim p As Product   p = New Product("PROASP3", "Professional ASP 3.0", 39.99)  

For the purposes of this example, let's define the classes within the ASP.NET page. Typically, you would define these in a separate compiled assembly. That topic was introduced in Chapters 3 and 4, and is covered in more detail in Chapter 17.

Creating the Collection Class

The ProductCollection class will support two key features:

  • Unordered enumeration of all the contained products.

  • Direct access of a product using a product code.

Since the Hashtable class provides the collection functionality necessary for implementing these features, you can use a Hashtable internally within your collection class for holding items. Then, you can aggregate the functionality of Hashtable and expose it, to provide access to your items in a type safe way that doesn't require casting.

Since an internal Hashtable is being used to hold the Product items, define a private field called _products of type Hashtable within the collection class. A new object instance is assigned to this field in the constructor:

  Dim _products as Hashtable   Public Sub New()   _products = New Hashtable()   End Sub  

Implementing the Add Method

The Add method allows a new Product to be added to the collection:

  Public Sub Add( Item as Product )   If Item Is Nothing Then   Throw New ArgumentException("Product cannot be null")   End If   _products.Add(Item.Code, Item)   End Sub  

This method throws an ArgumentException if a null item parameter is passed. If the parameter is not null , the Code property of the passed Product is used as the key for the Product in the contained Hashtable . Depending on your requirements, you could perform additional business logic validation here and throw additional exceptions.

Implementing the Remove Method

The Remove method removes a Product from the collection. The implementation of the method simply calls the Remove method of the Hashtable :

  Public Sub Remove(Item as Product)   _products.Remove(Item.Code)   End Sub  

Implementing the Item Property

The Item property allows a Product to be retrieved from, or added to the collection by specifying the product code:

  Public Default Property Item(Code as String) as Product   Get   Item = CType(_products(Code), Product)   End Get   Set   Add(Value)   End Set   End Property  

The implementation of the Set accessor calls the Add method in order to add the new product to the internal Hashtable . The process is implemented in a way so that any business logic in the Add method (such as the Null check) is neither duplicated nor missed.

Implementing the GetEnumerator Method

To use your collection class with the for..each statements in VB.NET and C#, your collection class must have a method called GetEnumerator . Although not strictly necessary, the IEnumerable interface is also implemented using the VB.NET Implements keyword. This is good practice and requires very little work:

  Public Class ProductCollection   Implements IEnumerable   ' ...   ' implement an enumerator for the products   Public Function GetEnumerator() As IEnumerator   Implements IEnumerable.GetEnumerator   GetEnumerator = _products.Values.GetEnumerator()   End Function   ' ...   End Class  

The GetEnumerator method has to return an enumerator object that implements the IEnumerator interface. You could implement this interface by creating another class, but since your collection class is using a Hashtable internally, it makes much more sense to reuse the enumerator object provided by that class when its values are enumerated. The Hashtable.Values property returns an ICollection interface and since the ICollection interface derives from IEnumerable , you can call GetEnumerator to create an enumerator object for the collection of values.

Using the Collection Class

With the Product and ProductCollection classes created, you can use them just like the other collections in this chapter, but this time with no casting. For example:

  ' Page-level variable   Dim _products as ProductCollection = New ProductCollection     Sub Page_Load(sender as Object, events As EventArgs)   ' Runs when page is loaded     Dim products As New ProductCollection()   Dim p As product     p = New Product("CAR", "A New Car", 19999.99)   products.Add(p)     p = New Product("HOUSE", "A New House", 299999.99)   products.Add(p)     p = New Product("BOOK", "A New Book", 49.99)   products(p.Code) = p     For Each p In products   outResult1.InnerHtml &= p.Code & " - " & p.Description _   & " - $" & p.Price & "<br />"   Next     products.Remove( products("HOUSE") )   For Each p In products   outResult2.InnerHtml &= p.Code & " - " & p.Description _   & " - $" & p.Price & "<br />"   Next     outResult3.InnerHtml = "Description for code CAR is: " _   & products("CAR").Description     End Sub  

The complete code for this example is contained in the samples available for download, in both VB.NET and C#. The result of running this page is shown in Figure 15-16:

click to expand
Figure 15-16:

The DictionaryBase and CollectionBase Classes

The DictionaryBase and CollectionBase classes allow you to create a Hashtable or ArrayList collection that can validate, and therefore restrict, the types it contains. It's a simple process to create your own collection class by deriving from these classes.

This simple ASP.NET page defines a MyStringCollection collection class, adds three strings and one integer, and then displays the contents:

  'our custom collection   Class MyStringCollection   Inherits CollectionBase   ... implementation goes here...   End Class     Dim names As IList     names = New MyStringCollection     names.Add("Richard")   names.Add("Alex")   names.Add("Dave")     Try   names.Add(2002)   Catch e As Exception   outResult1.InnerHtml = "Error: " & e.Message   End Try     For Each name As String in names   outResult2.InnerHtml &= name & "<br />"   Next  

The Collection base class implements the IList and ICollection interfaces. All the members of these interfaces are defined explicitly, which is why in the sample code the names variables have been defined as type IList .

Each of the collection base classes provides a number of virtual functions that are called when the collection is modified. For example, OnClear is called when a collection is cleared; OnInsert is called when an item is added; OnRemove when an item is deleted, and so on. By overriding one of these methods, you can perform additional checks and throw an exception if an undesired condition arises. For example, in the collection class, you could implement an OnInsert method that throws an ArgumentException if anything other than a string is added:

 Class MyStringCollection       Inherits CollectionBase  Overrides Protected Sub OnInsert(index as Integer, item as Object)   If Not(TypeOf item Is String) Then   Throw New ArgumentException("My collection only supports strings")   End If   End Sub  End Class 

Figure 15-17 shows the results of running this code:

click to expand
Figure 15-17:

The DictionaryBase class is used in the same way as the CollectionBase class and implements the IDictionary and ICollection interfaces.

The ReadOnlyCollectionBase Class

The ReadOnlyCollectionBase class provides functionality for exposing a read-only collection. The class implements the ICollection and IEnumerable interface. The items exposed are internally held in a protected ArrayList variable called InnerList . To use this class, you have to derive your own class from it, and populate the contents of the InnerList array.

Disposable Enumerators

When you enumerate a collection, the enumerator objects that implement the IEnumerator interface may require expensive resources. For example, depending on how underlying items are stored, a custom enumerator could be using a database connection, or be holding temporary files on disk. In these scenarios, it is important that the enumerable object releases resources it holds as soon as possible.

Due to the non-deterministic way the CLR releases object references, any code you write that directly uses an IEnumerator interface must always check if the enumerator objects that provided the interface support the IDisposable interface. You must then call the Dispose method when you've finished with the enumerator. If you do not do this, the resources held by the enumerator may not be released for some time. When you use the for..each language statement in C# and VB.NET, this is done automatically .

When you use the IEnumerator interface directly (or any other enumerable type), if you do not know whether an enumerator object supports the IDisposable interface, always check once you have finished with it. For example, in C#, you might write:

  IEnumerator e = c.GetEnumerator();   try   {   while (e.MoveNext())   {   Foo x = e.Current;   // ...   }   }   finally   {   IDisposable d = e as IDisposable;   if (d != null) d.Dispose();   }  

If you know that an enumerator object supports IDisposable , you can call it directly:

  IEnumerator e = c.GetEnumerator();   try   {   while (e.MoveNext())   {   Foo x = e.Current;   // ...   }   }   finally   {   ((IDisposable)e).Dispose();   }  



Professional ASP. NET 1.1
Professional ASP.NET MVC 1.0 (Wrox Programmer to Programmer)
ISBN: 0470384611
EAN: 2147483647
Year: 2006
Pages: 243

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net