Search - Articles
DevASP.NET for ASP.NET, VB.NET, XML and C# (C-Sharp) Developers Tuesday, September 02, 2014

Dev Articles
Search Directory
ASP.NET
VB.Net
C-Sharp
SQL Server
 

Delegates


There are times when it would be nice to be able to pass a procedure as a parameter to a method. The classic case is when building a generic sort routine, where we not only need to provide the data to be sorted, but we need to provide a comparison routine appropriate for the specific data.

 

It is easy enough to write a sort routine that sorts Person objects by name, or to write a sort routine that sorts SalesOrder objects by sales date. However, if we want to write a sort routine that can sort any type of object based on arbitrary sort criteria, that gets pretty difficult. At the same time, it would be nice to do, since some sort routines can get very complex and it would be nice to reuse that code without having to copy-and-paste it for each different sort scenario.

 

By using delegates, we can create such a generic routine for sorting – and in so doing we can see how delegates work and can be used to create many other types of generic routines.


The concept of a delegate formalizes the process of declaring a routine to be called and calling that routine.

 

The underlying mechanism used by the .NET environment for callback methods is the delegate. VB.NET uses delegates behind the scenes as it implements the Event, RaiseEvent, WithEvents, and Handles keywords.

Declaring a Delegate

In our code, we can declare what a delegate procedure must look like from an interface standpoint. This is done using the Delegate keyword. To see how this can work, let's create a routine to sort any kind of data.

 

To do this, we'll declare a delegate that defines a method signature for a method that compares the value of two objects and returns a Boolean indicating whether the first object has a larger value that the second object. We'll then create a sort algorithm that uses this generic comparison method to sort data. Finally, we'll create an actual method that implements the comparison and we'll pass the address of that method to the sort routine.

 

Add a new module to our project by choosing the Project | Add Module menu option. Name the module Sort.vb and then add the following code:

 

Module Sort

 

Public Delegate Function Compare(ByVal v1 As Object, ByVal v2 As Object) _

As Boolean

 

End Module

 

This line of code does something interesting. It actually defines a method signature as a data type. This new data type is named Compare and it can be used within our code to declare variables or parameters that will be accepted by our methods. A variable or parameter declared using this data type can actually hold the address of a method that matches the defined method signature – and we can then invoke that method by using the variable.

 

Any method with the signature:

 

f(Object, Object)

 

Can be viewed as being of type Compare.

Using the Delegate Data Type

We can write a routine that accepts this data type as a parameter – meaning that anyone calling our routine must pass us the address of a method that conforms to this interface. Add the following sort routine to the code module:

 

Public Sub DoSort(ByVal theData() As Object, ByVal GreaterThan As Compare)

Dim outer As Integer

Dim inner As Integer

Dim temp As Object

 

For outer = 0 To UBound(theData)

For inner = outer + 1 To UBound(theData)


 

If GreaterThan.Invoke(theData(outer), theData(inner)) Then

temp = theData(outer)

theData(outer) = theData(inner)

theData(inner) = temp

End If

Next

Next

End Sub

 

The GreaterThan parameter is a variable that holds the address of a method matching the method signature defined by our Compare delegate. The address of any method with a matching signature can be passed as a parameter to our Sort routine.

 

Note the use of the Invoke method, which is the way a delegate is called from our code. Also note that the routine deals entirely with the generic System.Object data type rather than with any specific type of data. The specific comparison of one object to another is left to the delegate routine that is passed in as a parameter.

Implementing a Delegate Method

All that remains is to actually create the implementation of the delegate routine and call our sort method. On a very basic level, all we need to do is create a method that has a matching method signature. For instance, we could create a method such as:

 

Public Function PersonCompare(ByVal Person1 As Object, _

ByVal Person2 As Object) As Boolean

 

End Function

 

The method signature of this method exactly matches that which we defined by our delegate earlier:

 

Compare(Object, Object)

 

In both cases, we're defining two parameters of type Object.

 

Of course, there's more to it than simply creating the stub of a method. We know that the method needs to return a value of True if its first parameter is greater than the second parameter, but otherwise should be written to deal with some specific type of data.

 

The Delegate statement defines a data type based on a specific method interface. To call a routine that expects a parameter of this new data type, it must pass us the address of a method that conforms to the defined interface.

 

To conform to the interface, a method must have the same number of parameters with the same data types as we've defined in our Delegate statement. Additionally, the method must provide the same return type as defined. The actual name of the method doesn't matter – it is the number, order, and data type of the parameters and return value that count.

 

To find the address of a specific method, we can use the AddressOf operator. This operator returns the address of any procedure or method, allowing us to pass that value as a parameter to any routine that expects a delegate as a parameter.


Our Person class already has a shared method named CompareAge that generally does what we want. Unfortunately, it accepts parameters of type Person rather than of type Object as required by the Compare delegate. We can use method overloading to solve this problem.

 

Create a second implementation of CompareAge that accepts parameters of type Object as required by the delegate, rather than of type Person as we have in the existing implementation:

 

Public Shared Function CompareAge(ByVal Person1 As Object, _

ByVal Person2 As Object) As Boolean

 

Return CType(Person1, Person).Age > CType(Person2, Person).Age

 

End Function

 

This method simply returns True if the first Person object's age is greater than the second. The routine accepts two Object parameters rather than specific Person type parameters, so we have to use the CType() method to access those objects as type Person. We accept the parameters as type Object because that is what is defined by the Delegate statement. We are matching its method signature:

 

f(Object, Object)

 

Since this method's parameter data types and return value match the delegate, we can use it when calling the sort routine. Place a button on the form and write the following code behind that button:

 

Private Sub Button2_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles button2.Click

Dim myPeople(4) As Person

 

myPeople(0) = New Person("Fred", #7/9/1960#)

myPeople(1) = New Person("Mary", #1/21/1955#)

myPeople(2) = New Person("Sarah", #2/1/1960#)

myPeople(3) = New Person("George", #5/13/1970#)

myPeople(4) = New Person("Andre", #10/1/1965#)

 

DoSort(myPeople, AddressOf Person.CompareAge)

End Sub

 

This code creates an array of Person objects and populates them. It then calls the DoSort routine from our module, passing the array as the first parameter and the address of our shared CompareAge method as the second. To display the contents of the sorted array in the IDE's output window, we can add the following code:

 

Private Sub button2_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles button2.Click

Dim myPeople(4) As Person

 

myPeople(0) = New Person("Fred", #7/9/1960#)

myPeople(1) = New Person("Mary", #1/21/1955#)

myPeople(2) = New Person("Sarah", #2/1/1960#)


 

myPeople(3) = New Person("George", #5/13/1970#)

myPeople(4) = New Person("Andre", #10/1/1965#)

 

DoSort(myPeople, AddressOf Person.CompareAge)

 

Dim myPerson As Person

 

For Each myPerson In myPeople

System.Diagnostics.Debug.WriteLine(myPerson.Name & " " & myPerson.Age)

Next

End Sub

 

When we run the application and click the button, the output window will display a list of the people, sorted by age:

 

 

What makes this whole thing very powerful is that we can change the comparison routine without changing the sort mechanism. Simply add another comparison routine to the Person class:

 

Public Shared Function CompareName(ByVal Person1 As Object, _

ByVal Person2 As Object) As Boolean

 

Return CType(Person1, Person).Name > CType(Person2, Person).Name

 

End Function

 

and then change the code behind the button on the form to use that alternate comparison routine:

 

Private Sub button2_Click(ByVal sender As System.Object, _

ByVal e As System.EventArgs) Handles button2.Click

 

Dim myPeople(4) As Person

 

myPeople(0) = New Person("Fred", #7/9/1960#)

myPeople(1) = New Person("Mary", #1/21/1955#)

myPeople(2) = New Person("Sarah", #2/1/1960#)

myPeople(3) = New Person("George", #5/13/1970#)

myPeople(4) = New Person("Andre", #10/1/1965#)

 

DoSort(myPeople, AddressOf Person.CompareName)

 

Dim myPerson As Person

 

For Each myPerson In myPeople

System.Diagnostics.Debug.WriteLine(myPerson.Name & " " & myPerson.Age)

Next

End Sub


When we run this updated code, we'll find that our array contains a set of data sorted by name rather than by age:

 

 

By simply creating a new compare routine and passing it as a parameter, we can entirely change the way that the data is sorted. Better still, this sort routine can operate on any type of object, as long as we provide an appropriate delegate method that knows how to compare that type of object.



DevASP.Net - Disclaimer - Privacy
2002-2010 DevASP.net