Search  
Wednesday, January 07, 2009 ..:: Articles ::.. Register  Login
 Article Details

Fantastic Four

The fantastic four are four interfaces that can breathe life into your business object classes.

Download code for this article

Setting the stage

.NET Framework comes with a rich barrage of built-in data types and also provides the developer a fertile platform for building custom types. .NET Framework offers 5 (6 in VB.NET) types to build these custom types; Class, Structure, Enum, Delegate, Interface (and Module in VB.NET).

When modeling real life problems, Classes (and to a lesser degree Structures) are what is predominately used to build types that map to the problem domain. Two main types of classes that most developers have come to know and expect to build are Business Object classes and Collection classes.

Business Object classes

Business Object classes are types that present singular entities like Person, Employee or Order. An example of a simple Business Object class is the Person type.

Public Class Person

 

   Public Sub New(ByVal firstName As String, ByVal lastName As String)

      m_firstName = firstName

      m_LastName = lastName

   End Sub

 

   Private m_firstName As String

   Public Property FirstName() As String

      Get

         Return m_firstName

      End Get

      Set(ByVal value As String)

         m_firstName = value

      End Set

   End Property

 

   Private m_LastName As String

   Public Property LastName() As String

      Get

         Return m_LastName

      End Get

      Set(ByVal value As String)

         m_LastName = value

      End Set

   End Property

 

   Public ReadOnly Property FullName() As String

      Get

         Return String.Format("{0} {1}", m_firstName, m_LastName)

      End Get

   End Property

 

   Public ReadOnly Property ReverseName() As String

      Get

         Return String.Format("{1}, {0}", m_firstName, m_LastName)

      End Get

   End Property

End Class

The Person type class has four read-only properties; FirstName, LastName, FullName and ReverseName.

Collection classes

Collection classes are types that present groupings of objects that need to be considered as a whole.

The Array type is an example of a collection type that requires predefining its size (the number of elements it will hold) before it can store objects.

      Dim peopleArr As Person()

      ReDim peopleArr(3)

      peopleArr(0) = New Person("Omar", "Shraim")

      peopleArr(1) = New Person("Bashar", "Lulu")

      peopleArr(2) = New Person("Fadi", "Atteya")

      peopleArr(3) = New Person("Mohammad", "Abdol-Haleem")

.NET brings with it a rich offering of collection types that do not require defining their sizes. Examples of these collection types are ArrayList, Hashtable, SortedList, Queue, Stack, Bitarray, BitVector32.

With the exception of BitArray and BitVector32 (each of which holds values of Boolean type) all these collection types can hold objects of only one type; System.Object (which means they can hold any object of any type).

      Dim peopleList As ArrayList = New ArrayList()

      peopleList.Add(New Person("Omar", "Shraim"))

      peopleList.Add(New Person("Bashar", "Lulu"))

      peopleList.Add(New Person("Fadi", "Atteya"))

      peopleList.Add(New Person("Mohammad", "Abdol-Haleem"))

If the developer needs to build collection types that hold values of types other than System.Object, .NET provides abstract collection types that can be extended to create Strongly Typed collections (collections that can only hold the type they are extended for).

Examples of abstract collection types are CollectionBase, ReadOnlycollectionBase, DictionayBase, NameObjectCollectionbase.

.NET Framework 2.0 brought with it Generic types that offered yet another richer and safer model of defining strongly typed collections with much less code.

      Dim peopleList2 As List(Of Person) = New List(Of Person)

      peopleList2.Add(New Person("Omar", "Shraim"))

      peopleList2.Add(New Person("Bashar", "Lulu"))

      peopleList2.Add(New Person("Fadi", "Atteya"))

      peopleList2.Add(New Person("Mohammad", "Abdol-Haleem"))

What’s missing?

Though it seems that our Person class models our real life person entity and we have an ample choice of collection types to hold Person type objects, we soon find out that our Person collections are lifeless because they dont support Searching and Sorting on the Person type objects they hold.

It sounds to reason that if an array or a collection of a built-in .NET type (like Integer or String) can be searched and sorted, so should an array or a collection of Person objects. Yet for any array or collection of Person type objects, the IndexOf() method (of the Array type) and Contains() method (of any collection type) would always return empty handed. The simple reason for this is that the array and collection types do not know how to compare a Person object.

      Dim peopleArr As Person()

      ReDim peopleArr(3)

      peopleArr(0) = New Person2.Person("Omar", "Shraim")

      peopleArr(1) = New Person2.Person("Bashar", "Lulu")

      peopleArr(2) = New Person2.Person("Fadi", "Atteya")

      peopleArr(3) = New Person2.Person("Mohammad", "Abdol-Haleem")

 

      ' IndexOf can never find the requested object

      If Array.IndexOf(peopleArr, New Person2.Person("Fadi", "Atteya")) >= 0 Then

         Console.WriteLine("FADI Found")

      Else

         Console.WriteLine("FADI Not Found")

      End If

To add this capability to our Person type we need to override the Equals() method (that all .NET types implicitly inherit from the System.Object type).

   Public Overrides Function Equals(ByVal obj As Object) As Boolean

 

      If (obj Is Nothing) OrElse (Not TypeOf obj Is Person) Then

         Return False

      End If

 

      Return String.Compare(Me.FullName, DirectCast(obj, Person).FullName, ignoreCase:=True) = 0

   End Function

Now the IndexOf() and Contains() methods can search for a Person object by comparing the FullName property of the objects in the collection.

Not the same but equal

The GetHashCode() method (all .NET types also implicitly inherit from the System.Object type) is responsible for generating an object’s hash code. For two distinct objects to be equal, they also should have the same hash code. A customary manner to generate a hash code is to combine the hash codes of two or more immutable fields.

   Private m_hashCode As Integer

 

   Public Sub New(ByVal firstName As String, ByVal lastName As String)

      m_firstName = firstName

      m_LastName = lastName

 

      'Person objects that have the same FirstName and LastName

      'also have the same hash codes.

      'use bitwise Xor to avoid overflow!

      m_hashCode = firstName.GetHashCode Xor lastName.GetHashCode

   End Sub

 

   Public Overrides Function GetHashCode() As Integer

      Return m_hashCode

   End Function

Now that our Person objects can be compared for equality, we need to give arrays and collections of Person objects the capability to sort their objects. This leads the way to the first of our Fantastic Four.

Enter the Fantastic Four

Our heroes are four interfaces that when implemented by a .NET class (or structure) would add some extra functionality. For example, collections of built-in .NET types (like Integer, String …) have the capability to sort the objects they hold out of the box because these built-in .NET types implement the first of our fantastic four interfaces; the IComparable interface.

IComparable

This interface exposes only one method, CompareTo(), which has the following signature:

      Function CompareTo(ByVal obj As Object) As Integer

The CompareTo() method is expected to return -1, 0, or 1 depending whether the object on which CompareTo() is called is less than, equal to, or greater than the object passed as an argument.

   ' this function adds sorting capabilities to the class

   Private Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo

      If obj Is Nothing Then Return 1

      Dim hold As Person = DirectCast(obj, Person)

      Return String.Compare(Me.FullName, hold.FullName, ignoreCase:=True)

   End Function

The implementation of CompareTo() in the Person type simply compares the FullName string and returns the result accordingly.

Now that the Person type implements ICompareTo, any collection of Person objects would magically become sortable.

      'sorting peopleArr from previous code

      Array.Sort(peopleArr)

 

      For i As Integer = 0 To 3

         Console.WriteLine(peopleArr(i).FullName)

      Next

IComparer

Collections of Person objects can now be sorted by the FullName property because that is the property used by the CompareTo() method. But what if we wanted collections of Person objects to be storable by other properties like the ReverseName property and the FirstName property?

It is very common to have Business Objects that need to be compared and sorted on different properties or even property combinations.

The second interface of our fantastic four solves this issue elegantly. The IComparer interface exposes only one method, Compare, which receives two object references and returns -1, 0 or 1 depending on whether the first object is less than, equal to or greater than the second object.

So if our Person type needs to be sorted on ReverseName and on FirstName, we need to build two comparer classes, ReverseNameComparer and FirstNameComparer.

A Comparer class is a class the implements the IComparer interface to compare objects of a specific type (which in our case is the Person type).

Public Class ComparerByFirstName

   Implements IComparer

 

   Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare

      ' two null objects are equal!

      If x Is Nothing AndAlso y Is Nothing Then

         Return 0

      End If

 

      ' a non-null object is always greater than a null object

      If x Is Nothing Then

         Return 1

      End If

      If y Is Nothing Then

         Return -1

      End If

 

      'if passed objects are not of Person type,

      'an exception is thrown

      Dim p1 As Person = DirectCast(x, Person)

      Dim p2 As Person = DirectCast(y, Person)

 

      Return String.Compare(p1.FirstName, p2.FirstName, ignoreCase:=True)

   End Function

End Class

 

Public Class ComparerByReverseName

   Implements IComparer

 

   Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare

      ' two null objects are equal!

      If x Is Nothing AndAlso y Is Nothing Then

         Return 0