EzBlog

Blogs from the mind of Tim Price

My Links

Blog Stats

Article Categories

Archives

Sorting & Filtering Custom Collections Part 1

Sorting and Filtering Custom Collections Part 1

By Tim Price

 

Requirements

 

Microsoft® Visual Studio® .NET 2003

Microsoft® Visual Basic .NET

 

Introduction

 

This is part one of a two part article; in this part we will create all the necessary class objects to add both filtering and sorting functionality to a custom collection, as well as the custom collection itself. Part 2 will create a .Net windows application that shows how this functionality can be offered to a user of our application. Of course, the application could just as easily be an ASP.net application.

 

The first thing we will create is a collection of strongly typed objects. We are going to create two objects, one of type Person and a collection object that will act as the container of Person objects, called Persons. This article assumes you are familiar with how and why you would create custom business and collection objects and is therefore, not covered by this article. If, you need more information on this, please read my other articles, Building Strongly Typed Nested Collections.

 

The Person Object

 

The person object is made up of three public properties and two overloaded New methods.

 

Public Class Person

 

#Region "Private Member Variables"

 

    Private m_sFirstName As String

    Private m_sLastName As String

    Private m_iAge As Integer

 

#End Region

 

#Region "Public Properties"

 

    Public Property FirstName() As String

        Get

            Return m_sFirstName

        End Get

        Set(ByVal Value As String)

            m_sFirstName = Value

        End Set

    End Property

 

    Public Property LastName() As String

        Get

            Return m_sLastName

        End Get

        Set(ByVal Value As String)

            m_sLastName = Value

        End Set

    End Property

 

    Public Property Age() As Integer

        Get

            Return m_iAge

        End Get

        Set(ByVal Value As Integer)

            m_iAge = Value

        End Set

    End Property

 

#End Region

 

#Region "Methods"

 

    Public Sub New()

        m_sFirstName = String.Empty

        m_sLastName = String.Empty

        m_iAge = 0

    End Sub

 

    Public Sub New(ByVal oNode As XmlNode)

        m_sFirstName = oNode.SelectSingleNode("FirstName").InnerText

        m_sLastName = oNode.SelectSingleNode("LastName").InnerText

        m_iAge = oNode.SelectSingleNode("Age").InnerText

    End Sub

 

#End Region

 

End Class

 

The Persons Collection Object

 

The collection class contains two overloaded New methods, used in object instantiation, a function for adding Person objects to the collection, three functions that return a Persons collection class; used for filtering and a sub routine to sort the collection.

 

First we will create the overloaded methods and the Add function.

 

Public Class Persons

    Inherits CollectionBase

 

#Region "Public Methods"

 

    Public Sub New()

 

    End Sub

 

    Public Sub New(ByVal oDOM As XmlDocument)

        Try

 

            Dim oNode As XmlNode

            For Each oNode In oDOM.DocumentElement.ChildNodes

                Dim oPerson As New Person(oNode)

                Add(operson)

            Next

        Catch ex As Exception

            Throw New Exception(ex.Message, ex.InnerException)

        End Try

    End Sub

 

    Public Function Add(ByVal oPerson As Person) As Integer

        Return InnerList.Add(oPerson)

    End Function

 

#End Region

 

There is nothing special or new by the above code and should be nothing new to you.

 

Providing Filtering

 

The filtering code is a little more complex, but again, should not cause any headaches to a developer who is familiar with working with custom collections.

 

First we create two object variables, one of type Person and the other of type Persons. Then we use the person variable to iterate through the contents of the collection class.

 

For Each oPerson In Me

 

We then check the respective person public property with the passed in parameter, to see if they are equal. If they are, we just do a quick check to see if the Persons variable has been instantiated and instantiate, if we need to.

 

If oPerson.FirstName.ToLower = sFirstname.ToLower Then

If oPersons Is Nothing Then

            oPersons = New Persons

      End If

 

The person variable we are using to iterate is then added to the newly created Persons collection, by calling it’s Add method.

 

oPersons.Add(oPerson)

 

At the end of the iterating, the code needs to know what to return to the caller. It does this by determining whether or not the Persons collection is nothing, if it is, then we know that we did not get a match with any of the existing items in the collection. In this case we return Nothing as the return value, if we did get a match then we simply return the newly created Persons collection.

 

If oPersons Is Nothing Then

Return Nothing

Else

      Return oPersons

End If

 

Filtering functions need to be created for all the Person object public properties we want to allow the user to filter on. In this case it is the Person.Firstname, Person.Lastname and Person.Age public properties.

 

#Region "Filtering"

 

    Public Function FilterByFirstname(ByVal sFirstname As String) As Persons

        Try

            Dim oPerson As Person

            Dim oPersons As Persons

 

            For Each oPerson In Me

                If oPerson.FirstName.ToLower = sFirstname.ToLower Then

                    If oPersons Is Nothing Then

                        oPersons = New Persons

                    End If

                    oPersons.Add(oPerson)

                End If

            Next

            If oPersons Is Nothing Then

                Return Nothing

            Else

                Return oPersons

            End If

        Catch ex As Exception

            Throw New Exception(ex.Message, ex.InnerException)

        End Try

    End Function

 

    Public Function FilterByLastname(ByVal sLastname As String) As Persons

        Try

            Dim oPerson As Person

            Dim oPersons As Persons

 

            For Each oPerson In Me

                If oPerson.LastName.ToLower = sLastname.ToLower Then

                    If oPersons Is Nothing Then

                        oPersons = New Persons

                    End If

                    oPersons.Add(oPerson)

                End If

            Next

            If oPersons Is Nothing Then

                Return Nothing

            Else

                Return oPersons

            End If

        Catch ex As Exception

            Throw New Exception(ex.Message, ex.InnerException)

        End Try

    End Function

 

    Public Function FilterByAge(ByVal iAge As Integer) As Persons

        Try

            Dim oPerson As Person

            Dim oPersons As Persons

 

            For Each oPerson In Me

                If oPerson.Age = iAge Then

                    If oPersons Is Nothing Then

                        oPersons = New Persons

                    End If

                    oPersons.Add(oPerson)

                End If

            Next

            If oPersons Is Nothing Then

                Return Nothing

            Else

                Return oPersons

            End If

        Catch ex As Exception

            Throw New Exception(ex.Message, ex.InnerException)

        End Try

    End Function

 

#End Region

 

Providing Sorting

 

The sorting functionality is created by a sub routine called Sort, in this example the sort method accepts two parameters one of type string, which is the field we want to sort and the other is an integer, which represents the direction to sort. For example

 

  • 0 = Ascending
  • 1 = Descending

 

#Region "Sorting"

 

    Public Sub Sort(ByVal sSortItem As String, ByVal iDirection As Integer)

        Try

            Select Case sSortItem.ToUpper

                Case "FIRSTNAME"

                    Dim oComparer As New Comparer.FirstnameComparer(iDirection)

                    InnerList.Sort(oComparer)

                Case "LASTNAME"

                    Dim oComparer As New Comparer.LastnameComparer(iDirection)

                    InnerList.Sort(oComparer)

                Case "AGE"

                    Dim oComparer As New Comparer.AgeComparer(iDirection)

                    InnerList.Sort(oComparer)

            End Select

        Catch ex As Exception

            Throw New Exception(ex.Message, ex.InnerException)

        End Try

 

    End Sub

 

#End Region

 

Comparer Class

 

The real sorting of the collection is handled by a new class object called Comparer which contains three classes that in themselves perform the sorting of the collection. The properties we are going to allow sorting on are the same as the ones used for the filtering:

 

  1. Person.Firstname
  2. Person.Lastname
  3. Person.Age.

 

Each of these classes implements the IComparer interface. This interface provides a method for comparing two objects. It allows you to customize the sort order of a collection. This class has a Default function called Compare which implements the actual comparison.

 

Each of these classes has a New method which accepts a parameter of type integer, this variable represents the direction we want to sort the collection. When the New method is called it sets the value of an internal variable equal to the value of the parameter that was passed in. This variable is then used by the Compare function.

 

Class Comparer

   

    Class FirstnameComparer

        Implements IComparer

 

        Private m_iDirection As Integer

 

        Public Sub New(ByVal iDirection As Integer)

            m_iDirection = iDirection

        End Sub

 

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

            Dim oPerson1 As Person

            Dim oPerson2 As Person

 

            If GetType(Person).IsInstanceOfType(x) Then

                oPerson1 = CType(x, Person)

            Else

                Throw New Exception("Casting failed...")

            End If

 

            If GetType(Person).IsInstanceOfType(y) Then

                oPerson2 = CType(y, Person)

            Else

                Throw New Exception("Casting failed...")

            End If

 

            Select Case m_iDirection

                Case Enumerations.SortDirection.ASC

                    Return oPerson1.FirstName < oPerson2.FirstName

                Case Enumerations.SortDirection.DESC

                    Return oPerson1.FirstName > oPerson2.FirstName

            End Select

        End Function

    End Class

 

End Class

 

The compare function accepts two parameters of type object and returns an integer corresponding to the sort direction. It starts of by declaring two Person objects. Each of the parameters is tested to see if they are of type person by passing the name of a type (person) to the built in GetType operator and checking the IsInstanceOfType method, which in turn returns a Boolean value.

 

If GetType(Person).IsInstanceOfType(x) Then

 

If the parameter is of the type person, we need to  convert the object to type person and assign it to one of the variables created at the beginning of the function.

 

We then determine in which direction the user wants the collection sorting by using a select case statement. This statement uses a Public Enum, defined in a class called Enumerations and illustrated below.

 

Public Class Enumerations

    Public Enum SortDirection

        ASC = 0

        DESC = 1

    End Enum

End Class

 

Public Enums are out of the scope of this article, but a link is provided in the ‘Further Reading’ section at the end of this article. They simply ‘provide a convenient way to work with sets of related constants and to associate constant values with names’.

 

We then return to the caller an integer value equal to the direction of the sort. This in turn is passed as a parameter to the InnerList.Sort method, cast your mind back to the definition of the Sort method of the Persons class:

Dim oComparer As New Comparer.FirstnameComparer(iDirection)

InnerList.Sort(oComparer)

 

The remaining two classes are the same and only differ in the property of the person that we are sorting on and therefore do not need any further discussion.

 

Class LastnameComparer

        Implements IComparer

 

        Private m_iDirection As Integer

 

        Public Sub New(ByVal iDirection As Integer)

            m_iDirection = iDirection

        End Sub

 

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

            Dim oPerson1 As Person

            Dim oPerson2 As Person

 

            If GetType(Person).IsInstanceOfType(x) Then

                oPerson1 = CType(x, Person)

            Else

                Throw New Exception("Casting failed.")

            End If

 

            If GetType(Person).IsInstanceOfType(y) Then

                oPerson2 = CType(y, Person)

            Else

                Throw New Exception("Casting failed.")

            End If

 

            Select Case m_iDirection

                Case Enumerations.SortDirection.ASC

                    Return oPerson1.LastName < oPerson2.LastName

                Case Enumerations.SortDirection.DESC

                    Return oPerson1.LastName > oPerson2.LastName

            End Select

        End Function

    End Class

 

    Class AgeComparer

        Implements IComparer

 

        Private m_iDirection As Integer

 

        Public Sub New(ByVal iDirection As Integer)

            m_iDirection = iDirection

        End Sub

 

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

            Dim oPerson1 As Person

            Dim oPerson2 As Person

 

            If GetType(Person).IsInstanceOfType(x) Then

                oPerson1 = CType(x, Person)

            Else

                Throw New Exception("Casting failed.")

            End If