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.
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