Author Topic: Give Your Everyday Custom Collections a Design-Time Makeover  (Read 6419 times)

Offline admin

  • Administrator
  • Sr. Member
  • *****
  • Posts: 296
    • View Profile
Give Your Everyday Custom Collections a Design-Time Makeover
« on: December 20, 2007, 04:12:15 PM »
In the long-running debate over whether to use ADO.NET DataSets or custom business objects to represent data within a .NET-based application, DataSets have always held a couple of strategic advantages over custom objects: data binding and design-time support. Rather than engage in that debate, which would take an entire article in itself, I'm going to show how you can level the playing field by providing data binding support for custom collections to enable sorting, searching, and editing in as simple a manner as possible. I will also show how to make all of these features available in the Windows® and Web Forms Designers, just like an ADO.NET DataSet. Lastly, I'll take a close look at new functionality available in the Microsoft® .NET Framework 2.0 and then show how much easier it is to implement the same functionality using generics.



Why Use Custom Collections?

When working with data in a .NET-based application, it's often useful to create lists of objects. The .NET Framework provides standard list and dictionary types such as ArrayList and Hashtable that allow you to organize objects of any type into collections. You can then bind these collections to Windows Forms or ASP.NET controls at run time. However, because these collections are built to hold any type of object, there is no way to enforce a rule that limits a collection to containing only objects of a specific type. For example, you cannot define an ArrayList that only contains objects of type Order. Because of this, when developers use the list they have to check or cast the types being returned from the collection to ensure the object is of the type they are expecting. If this check isn't done and an object of the wrong type was added to the collection, exceptions can result. Custom collections—specialized classes based on the .NET Framework collections—let you enforce type safety by limiting collections to accepting only objects of a specific type. This in turn makes the collection easier to use.

Unfortunately, none of the .NET Framework collection classes that you can use to build your custom collection provide the level of design-time data binding support that the DataSet provides. By implementing a few interfaces, however, you can provide that level of functionality to your collection.



Student Business Object

For the examples, I've created a business object called Student which has properties for FirstName, LastName, Age, and Grade. Figure 1 shows the source for this class. It includes a default public constructor as well as an overloaded constructor for creating an instance and setting its properties immediately. Also, you can see that there is a PropertyChanged event defined for each property and that this event is raised in the corresponding property's Set block. These are good basic business object design patterns.

Code: [Select]
Public Class Student

    Private m_FirstName As String
    Private m_LastName As String
    Private m_Grade As Integer
    Private m_Age As Integer

    Public Event FirstNameChanged As System.EventHandler
    Public Event LastNameChanged As System.EventHandler
    Public Event GradeChanged As System.EventHandler
    Public Event AgeChanged As System.EventHandler

    Public Sub New()
    End Sub

    Public Sub New(ByVal FirstName As String, ByVal LastName As String, _
                   Optional ByVal Grade As Integer = 0, _
                   Optional ByVal Age As Integer = 0)
        With Me
            .m_FirstName = FirstName
            .m_LastName = LastName
            .m_Grade = Grade
            .m_Age = Age
        End With
    End Sub

    Public Property FirstName() As String
        Get
            Return m_FirstName
        End Get
        Set(ByVal Value As String)
            m_FirstName = Value
            RaiseEvent FirstNameChanged(Me, EventArgs.Empty)
        End Set
    End Property

    Public Property LastName() As String
        Get
            Return m_LastName
        End Get
        Set(ByVal Value As String)
            m_LastName = Value
            RaiseEvent LastNameChanged(Me, EventArgs.Empty)
        End Set
    End Property

    Public Property Grade() As Integer
        Get
            Return m_Grade
        End Get
        Set(ByVal Value As Integer)
            m_Grade = Value
            RaiseEvent GradeChanged(Me, EventArgs.Empty)
        End Set
    End Property

    Public Property Age() As Integer
        Get
            Return m_Age
        End Get
        Set(ByVal Value As Integer)
            m_Age = Value
            RaiseEvent AgeChanged(Me, EventArgs.Empty)
        End Set
    End Property

End Class

Figure 1 The Student Class

Offline admin

  • Administrator
  • Sr. Member
  • *****
  • Posts: 296
    • View Profile
Re: Give Your Everyday Custom Collections a Design-Time Makeover
« Reply #1 on: December 20, 2007, 04:17:04 PM »
Creating a Type-Safe Custom Collection

Now, let's create a custom collection that enforces type safety by only allowing objects of type Student to be added to the list. When you create a custom list collection, follow the naming standard of adding the word "Collection" after the object type. In the case of Student, the collection would be called StudentCollection.

There are several ways to create a custom collection. You can create a class that derives directly from one of the standard collection classes, such as ArrayList, and then hide the methods that are not type safe and properties that reference items in the list as Object using the Shadows (Visual Basic®) or new (C#) keyword and replace them with your type-specific versions. You can also create a class that simply implements the IList interface, leaving the internal storage of items in the list entirely up to you, but requiring a bit more code. The preferred method, however, is to derive the custom collection from the CollectionBase class.

The CollectionBase class implements many of the methods and properties that a collection might need, such as Clear and Count, but it leaves the specification of the various type-specific methods to the developer. These methods include Add, Remove, and Insert, as well as the indexer property Item. The CollectionBase class includes a protected property called List, which returns a reference to the CollectionBase's IList implementation (this wraps an internal ArrayList that is not type safe). In the implementation of the public type-specific methods for your collection, you simply wrap the methods of the CollectionBase.List property that aren't type safe with the publicly exposed type safe version. As an example, the StudentCollection.Add method would look like this:

Code: [Select]
Public Sub Add(ByVal Student As Student)
    Me.List.Add(Student)    'List.Add() takes type Object
End Sub

Figure 2 shows the implementation of the StudentCollection class that ensures that the collection only holds objects of type Student. Note that the Item property uses the Default keyword. This makes it easier for developers who use the collection to access items in the list using the array indexing syntax, as demonstrated in the following line of code:
Code: [Select]
Dim FirstStudent As Student = myStudentCollection(3) ' get fourth item


Code: [Select]
Imports System.Collections

Public Class StudentCollection
    Inherits CollectionBase

    Public Sub New()
    End Sub

    Public Sub Add(ByVal Student As Student)
        Me.List.Add(Student)    'List.Add() takes type Object
    End Sub

    Public Function Contains(ByVal Student As Student) As Boolean
        Return Me.List.Contains(Student)
    End Function

    Public Function IndexOf(ByVal student As Student) As Integer
        Return Me.List.IndexOf(student)
    End Function

    Public Sub Insert(ByVal index As Integer, ByVal Student As Student)
        Me.List.Insert(index, Student)
    End Sub

    Default Public Property Item(ByVal index As Integer) As Student
        Get
            Return CType(Me.List.Item(index), Student)
        End Get
        Set(ByVal Value As Student)
            Me.List(index) = Value
        End Set
    End Property

    Public Sub Remove(ByVal Student As Student)
        Me.List.Remove(Student)
    End Sub

End Class


Figure 2 The StudentCollection Class

CollectionBase also implements a couple of very important interfaces, the first of which is called IEnumerable. The IEnumerable interface methods allow developers to work with the items in the collection using a For Each loop. It also implements the IList interface. This interface lets the runtime and the designers know that the class contains the standard list methods such as Add, Remove, and Count that are necessary for data binding. Any object that implements the IList interface can be bound to list-using UI controls, such as Datagrids, Listboxes, and Comboboxes. Therefore, the code presented thus far is all that is necessary to provide a type-safe collection that can be one-way bound at run time. But what about design time?