In VB.NET, a delegate is a reference type that represents a method with a specific signature and return type. You can encapsulate any matching method in that delegate. A delegate is created with the keyword Delegate, followed by a return type and the signature of the methods that can be delegated to it, as in the following:
Public Delegate Function WhichIsSmaller( _
ByVal obj1 As Object, ByVal obj2 As Object) As Comparison
The keyword Public declares the delegate to be a public member of the class. The keyword Function indicates that the delegate will be used to encapsulate a method that returns a value. The identifier WhichIsSmaller is the name of this delegate.
The values within the parentheses are the parameters of the methods this delegate will encapsulate, or represent. That is, this delegate may encapsulate any method that takes two objects as parameters.
The final keywords As Comparison specify the return type of the methods that can be encapsulated by this delegate, in this case an enumeration. Comparison is the identifier of the enumeration you'll define:
Public Enum Comparison theFirst = 1 theSecond = 2 End Enum
The method you encapsulate with this delegate must return either Comparison.theFirst or Comparison.theSecond.
In total, the statement shown previously defines a public delegate named WhichIsSmaller that encapsulates functions that take two objects as parameters, and that return an instance of the enumerated type Comparison. You can encapsulate any matching method in an instance of this delegate.
Once the delegate is defined, you can encapsulate a member method with that delegate by instantiating the delegate, passing in as a parameter the name of a method that matches the return type and signature.
Suppose, for example, that you want to create a simple container class called a Pair that can hold and sort any two objects passed to it. You can't know in advance what kind of objects a Pair will hold, but by creating methods within those objects to which the sorting task can be delegated, you can delegate responsibility for determining their order to the objects themselves.
Different objects will sort differently; for example, a Pair of counter objects might sort in numeric order, while a Pair of Buttons might sort alphabetically by their name.
What a nightmare this will be for the creator of the Pair class. The class must know how each type of object sorts. If you add a Button, the Pair class must know to ask the Buttons for their names and sort them alphabetically. If you then add a pair of Employees, the Pair class must know to ask the Employees for their date of hire, and sort by date. There must be a better way!
The answer is to delegate this responsibility to the objects themselves. If the Button objects know which Button comes first and the Employee objects know which Employee comes first, then the Pair class becomes much more flexible. This is the essence of good object-oriented design: delegate responsibility to the class that is in the best position to have the knowledge required.
Delegating the responsibility for knowing how the objects are sorted to the objects themselves decouples the Pair class from the types contained in the Pair. The Pair no longer needs to know how the objects are sorted; it just needs to know that they can be sorted.
Of course, the objects you put in the Pair container must know how to tell the Pair which object comes first. The Pair container needs to specify the method these objects must implement. Rather than specifying a particular method, however, the Pair will just specify the signature and return type of the method. That is, the Pair will say, in essence, "I can hold any type of object that offers a method that takes two objects and returns an enumerated value, signifying which comes first.
You create a delegate that defines the signature and return type for the object's method. The object (e.g., Button) must provide this method with this signature in order to allow the Pair to determine which object should be first and which should be second.
In the case of our example, the Pair class defines a delegate, WhichIsSmaller. The Sort( ) method will take as a parameter an instance of the WhichIsSmaller delegate. When the Pair needs to know how to order its objects it will invoke the delegate, passing in its two objects as parameters. The responsibility for deciding which of the two objects is smaller (and thus comes first) to the method encapsulated by the delegate.
To test the delegate, create two classes: a Dog class and a Student class. Dogs and Students have little in common, except that they both implement methods that can be encapsulated by WhichIsSmaller; thus both Dog objects and Student objects are eligible to be held within Pair objects.
In the test program, you will create a couple of Students and a couple of Dogs, and store them in Pairs. You will then create delegate objects to encapsulate their respective methods that match the delegate signature and return type, and you'll ask the Pair objects to sort the Dog and Student objects. Example 12-1 shows a complete program illustrating the use of delegates. This is a long and somewhat complicated program that will be analyzed in detail following the output.
Option Strict On Imports System Namespace DelegatesAndEvents Public Enum Comparison theFirst = 1 theSecond = 2 End Enum ' a simple collection to hold 2 items Public Class Pair ' private array to hold the two objects Private thePair(2) As Object ' the delegate declaration Public Delegate Function WhichIsSmaller( _ ByVal obj1 As Object, ByVal obj2 As Object) As Comparison ' passed in constructor take two objects, ' added in order received Public Sub New( _ ByVal firstObject As Object, _ ByVal secondObject As Object) thePair(0) = firstObject thePair(1) = secondObject End Sub ' public method which orders the two objects ' by whatever criteria the object likes! Public Sub Sort(ByVal theDelegatedFunc As WhichIsSmaller) If theDelegatedFunc(thePair(0), thePair(1)) = _ Comparison.theSecond Then Dim temp As Object = thePair(0) thePair(0) = thePair(1) thePair(1) = temp End If End Sub ' public method which orders the two objects ' by the reverse of whatever criteria the object likes! Public Sub ReverseSort(ByVal theDelegatedFunc As WhichIsSmaller) If theDelegatedFunc(thePair(0), thePair(1)) = _ Comparison.theFirst Then Dim temp As Object = thePair(0) thePair(0) = thePair(1) thePair(1) = temp End If End Sub ' ask the two objects to give their string value Public Overrides Function ToString( ) As String Return thePair(0).ToString( ) & ", " & thePair(1).ToString( ) End Function End Class Public Class Dog Private weight As Integer Public Sub New(ByVal weight As Integer) Me.weight = weight End Sub ' dogs are ordered by weight Public Shared Function WhichDogIsSmaller( _ ByVal o1 As Object, ByVal o2 As Object) As Comparison Dim d1 As Dog = DirectCast(o1, Dog) Dim d2 As Dog = DirectCast(o2, Dog) If d1.weight > d2.weight Then Return Comparison.theSecond Else Return Comparison.theFirst End If End Function Public Overrides Function ToString( ) As String Return weight.ToString( ) End Function End Class Public Class Student Private name As String Public Sub New(ByVal name As String) Me.name = name End Sub ' students are ordered alphabetically Public Shared Function WhichStudentIsSmaller( _ ByVal o1 As Object, ByVal o2 As Object) As Comparison Dim s1 As Student = DirectCast(o1, Student) Dim s2 As Student = DirectCast(o2, Student) If String.Compare(s1.name, s2.name) < 0 Then Return Comparison.theFirst Else Return Comparison.theSecond End If End Function Public Overrides Function ToString( ) As String Return name End Function End Class Class Tester Public Sub Run( ) ' create two students and two dogs ' and add them to Pair objects Dim Jesse As New Student("Jesse") Dim Stacey As New Student("Stacey") Dim Milo As New Dog(65) Dim Fred As New Dog(12) Dim studentPair As New Pair(Jesse, Stacey) Dim dogPair As New Pair(Milo, Fred) Console.WriteLine("studentPair: {0}", _ studentPair.ToString( )) Console.WriteLine("dogPair: {0}", _ dogPair.ToString( )) ' Instantiate the delegates Dim theStudentDelegate As New _ Pair.WhichIsSmaller(AddressOf Student.WhichStudentIsSmaller) Dim theDogDelegate As New _ Pair.WhichIsSmaller(AddressOf Dog.WhichDogIsSmaller) ' sort using the delegates studentPair.Sort(theStudentDelegate) Console.WriteLine("After Sort studentPair: {0}", _ studentPair.ToString( )) studentPair.ReverseSort(theStudentDelegate) Console.WriteLine("After ReverseSort studentPair: {0}", _ studentPair.ToString( )) dogPair.Sort(theDogDelegate) Console.WriteLine("After Sort dogPair: {0}", _ dogPair.ToString( )) dogPair.ReverseSort(theDogDelegate) Console.WriteLine("After ReverseSort dogPair: {0}", _ dogPair.ToString( )) End Sub Public Shared Sub Main( ) Dim t As New Tester( ) t.Run( ) End Sub End Class End Namespace Output: studentPair: Jesse, Stacey dogPair: 65, 12 After Sort studentPair: Jesse, Stacey After ReverseSort studentPair: Stacey, Jesse After Sort dogPair: 12, 65 After ReverseSort dogPair: 65, 12
Example 12-1 begins by creating a Pair constructor that takes two objects and stashes them away in a private array:
Public Class Pair Private thePair(2) As Object Public Sub New( _ ByVal firstObject As Object, _ ByVal secondObject As Object) thePair(0) = firstObject thePair(1) = secondObject End Sub
You override ToString( ) to obtain the string value of the two objects:
Public Overrides Function ToString( ) As String Return thePair(0).ToString( ) & ", " & thePair(1).ToString( ) End Function
You now have two objects in your Pair and you can display their values. You're ready to sort them and display the results of the sort.
You create the delegate WhichIsSmaller that defines the signature for the sorting method, as described previously:
Public Delegate Function WhichIsSmaller( _ ByVal obj1 As Object, ByVal obj2 As Object) As Comparison
The return value is of type Comparison, the enumeration defined earlier in the file:
Public Enum Comparison theFirst = 1 theSecond = 2 End Enum
Any method that takes two objects and returns a comparison can be encapsulated by this delegate at runtime.
You can now define the Sort( ) method for the Pair class:
Public Sub Sort(ByVal theDelegatedFunc As WhichIsSmaller) If theDelegatedFunc(thePair(0), thePair(1)) = _ Comparison.theSecond Then Dim temp As Object = thePair(0) thePair(0) = thePair(1) thePair(1) = temp End If End Sub
This method takes a parameter: a delegate of type WhichIsSmaller named theDelegatedFunc. The Sort( ) method delegates responsibility for deciding which of the two objects in the Pair comes first to the method encapsulated by that delegate. In the body of the Sort( ) method it invokes the delegated method and examines the return value, which will be one of the two enumerated values of comparison.
If the value returned is theSecond, the objects within the pair are swapped; otherwise no action is taken.
Notice that theDelegatedFunc is the name of the parameter to represent the method encapsulated by the delegate. You can assign any method (with the appropriate return value and signature) to this parameter. It is as if you had a method that took an integer as a parameter:
Public Function SomeMethod (myParam As Integer)
The parameter name is myParam, but you can pass in any integer value or variable. Similarly the parameter name in the delegate example is theDelegatedFunc, but you can pass in any method that meets the return value and signature defined by the delegate WhichIsSmaller.
Imagine you are sorting students by name. You write a method that returns Comparison.theFirst if the first student's name comes first and Comparison.theSecond if the second student's name does. If you pass in "Amy, Beth" the method will return Comparison.theFirst, and if you pass in "Beth, Amy" it will return Comparison.theSecond. If you get back Comparison.theSecond, the Sort( ) method reverses the items in its array, setting Amy to the first position and Beth to the second.
Now add one more method, ReverseSort( ), which will put the items into the array in reverse order:
Public Sub ReverseSort(ByVal theDelegatedFunc As WhichIsSmaller) If theDelegatedFunc(thePair(0), thePair(1)) = _ Comparison.theFirst Then Dim temp As Object = thePair(0) thePair(0) = thePair(1) thePair(1) = temp End If End Sub
The logic here is identical to the Sort( ), except that this method performs the swap if the delegated method says that the first item comes first. Because the delegated function thinks the first item comes first, and this is a reverse sort, the result you want is for the second item to come first.
This time if you pass in "Amy, Beth," the delegated function returns Comparison.theFirst (i.e., Amy should come first), but because this is a reverse sort it swaps the values, setting Beth first. This allows you to use the same delegated function as you used with Sort( ), without forcing the object to support a function that returns the reverse sorted value.
Now all you need are some objects to sort. You'll create two absurdly simple classes: Student and Dog. Assign Student objects a name at creation:
Public Class Student Private name As String Public Sub New(ByVal name As String) Me.name = name End Sub
The Student class requires two methods, one to override ToString( ) and the other to be encapsulated as the delegated method.
Student must override ToString( ) so that the ToString( ) method in Pair, which invokes ToString( ) on the contained objects, will work properly. The implementation does nothing more than return the student's name (which is already a string object):
Public Overrides Function ToString( ) As String Return name End Function
It must also implement a method to which Pair.Sort( ) can delegate the responsibility of determining which of two objects comes first:
Public Shared Function WhichStudentIsSmaller( _ ByVal o1 As Object, ByVal o2 As Object) As Comparison Dim s1 As Student = DirectCast(o1, Student) Dim s2 As Student = DirectCast(o2, Student) If String.Compare(s1.name, s2.name) < 0 Then Return Comparison.theFirst Else Return Comparison.theSecond End If End Function
As you saw in Chapter 10, String.Compare( ) is a .NET Framework method on the String class that compares two strings and returns less than zero if the first is smaller, greater than zero if the second is smaller, and zero if they are the same.
Notice that the logic here returns Comparison.theFirst only if the first string is smaller; if they are the same or the second is larger, this method returns Comparison.theSecond.
Notice that the WhichStudentIsSmaller( ) method takes two objects as parameters and returns a comparison. This qualifies it to be a Pair.WhichIsSmaller delegated method, whose signature and return value it matches.
The second class is Dog. For our purposes, Dog objects will be sorted by weight, lighter dogs before heavier. Here's the complete declaration of Dog:
Public Class Dog Private weight As Integer Public Sub New(ByVal weight As Integer) Me.weight = weight End Sub ' dogs are ordered by weight Public Shared Function WhichDogIsSmaller( _ ByVal o1 As Object, ByVal o2 As Object) As Comparison Dim d1 As Dog = DirectCast(o1, Dog) Dim d2 As Dog = DirectCast(o2, Dog) If d1.weight > d2.weight Then Return Comparison.theSecond Else Return Comparison.theFirst End If End Function Public Overrides Function ToString( ) As String Return weight.ToString( ) End Function End Class
Notice that the Dog class also overrides ToString( ) and implements a shared method with the correct signature for the delegate. Notice also that the Dog and Student delegate methods do not have the same name. They do not need to have the same name, as they will be assigned to the delegate dynamically at runtime.
|
The Run( ) method creates two Student objects and two Dog objects and then adds them to Pair containers. The student constructor takes a string for the student's name and the dog constructor takes an integer for the dog's weight.
Public Sub Run( ) ' create two students and two dogs ' and add them to Pair objects Dim Jesse As New Student("Jesse") Dim Stacey As New Student("Stacey") Dim Milo As New Dog(65) Dim Fred As New Dog(12) Dim studentPair As New Pair(Jesse, Stacey) Dim dogPair As New Pair(Milo, Fred) Console.WriteLine("studentPair: {0}", _ studentPair.ToString( )) Console.WriteLine("dogPair: {0}", _ dogPair.ToString( ))
You display the contents of the two Pair containers to see the order of the objects. The output looks like this:
studentPair: Jesse, Stacey dogPair: 65, 12
As expected, the objects are in the order in which they were added to the Pair containers. We next instantiate two delegate objects:
Dim theStudentDelegate As New _ Pair.WhichIsSmaller( _ AddressOf Student.WhichStudentIsSmaller) Dim theDogDelegate As New _ Pair.WhichIsSmaller( _ AddressOf Dog.WhichDogIsSmaller)
The first delegate, theStudentDelegate, is created by passing in the appropriate method from the Student class. The second delegate, theDogDelegate, is passed a method from the Dog class.
Shared Versus Instance MethodsIn Example 12-1, the methods you encapsulated with the delegates were shared methods of the Student and Dog class: Public Shared Function WhichStudentIsSmaller( _ ByVal o1 As Object, ByVal o2 As Object) As Comparison You could declare the WhichStudentIsSmaller method to be an instance method instead: Public Function WhichStudentIsSmaller( _ ByVal o1 As Object, ByVal o2 As Object) As Comparison You can still encapsulate it as a delegate, but you must refer to it through an instance, rather than through the class: Dim theStudentDelegate As New _ Pair.WhichIsSmaller( AddressOf Jesse.WhichStudentIsSmaller) Which you use, instance or shared methods, is entirely up to you. The advantage of shared methods is that you don't need an instance of the class to create the delegate. |
The delegates are now objects that can be passed to methods. You pass the delegates first to the Sort method of the Pair object, and then to the ReverseSort method:
studentPair.Sort(theStudentDelegate) studentPair.ReverseSort(theStudentDelegate) dogPair.Sort(theDogDelegate) dogPair.ReverseSort(theDogDelegate)
The results are displayed on the console:
After Sort studentPair: Jesse, Stacey After ReverseSort studentPair: Stacey, Jesse After Sort dogPair: 12, 65 After ReverseSort dogPair: 65, 12
A disadvantage of Example 12-1 is that it forces the calling class, in this case Tester, to instantiate the delegates it needs in order to sort the objects in a Pair. Notice that in Example 12-1, within a method of Tester, you see this code:
Dim theStudentDelegate As New _ Pair.WhichIsSmaller( _ AddressOf Student.WhichStudentIsSmaller)
What is going on here is that the Tester class needs to know that it must instantiate an instance of the WhichIsSmaller delegate (declared in Pair), and that it must pass in the WhichStudentIsSmaller method of the Student class. Once it has created this delegate, it can invoke the sort by passing in the delegate it just created:
studentPair.Sort(theStudentDelegate)
Tester then goes on to instantiate a second delegate, passing in the WhichDogIsSmaller( ) method to create the delegate for the Dog objects, and invoking sort with that delegate as well:
Dim theDogDelegate As New _ Pair.WhichIsSmaller( _ AddressOf Dog.WhichDogIsSmaller) dogPair.Sort(theDogDelegate)
Rather than forcing Tester to know which method Student and Dog must use to accomplish the sort, it would be better to get the delegate from the Student or Dog classes themselves.
You can give the implementing classes (Student and Dog) the responsibility for instantiating the delegate by giving each implementing class its own shared delegate. In that case, rather than knowing which method implements the sort for the Student, Tester would only need to know that the Student class has a shared delegate named, for example, OrderStudents; then the author of the Tester class could write:
studentPair.Sort(Student.OrderStudents)
Thus, you can modify Student to add this:
Public Shared ReadOnly OrderStudents As New Pair.WhichIsSmaller( _ AddressOf Student.WhichStudentIsSmaller)
This creates a shared, read-only delegate named OrderStudents.
|
You can create a similar delegate within Dog:
Public Shared ReadOnly OrderDogs As _ New Pair.WhichIsSmaller( _ AddressOf Dog.WhichDogIsSmaller)
These are now shared fields of their respective classes. Each is pre-wired to the appropriate method within the class. You can invoke delegates without declaring a local delegate instance. You just pass in the shared delegate of the class:
studentPair.Sort(Student.OrderStudents) studentPair.ReverseSort(Student.OrderStudents)
The complete listing is shown in Example 12-2.
Option Strict On Imports System Namespace DelegatesAndEvents Public Enum Comparison theFirst = 1 theSecond = 2 End Enum ' a simple collection to hold 2 items Public Class Pair ' private array to hold the two objects Private thePair(2) As Object ' the delegate declaration Public Delegate Function WhichIsSmaller( _ ByVal obj1 As Object, ByVal obj2 As Object) As Comparison ' passed in constructor take two objects, ' added in order received Public Sub New( _ ByVal firstObject As Object, _ ByVal secondObject As Object) thePair(0) = firstObject thePair(1) = secondObject End Sub ' public method which orders the two objects ' by whatever criteria the object likes! Public Sub Sort(ByVal theDelegatedFunc As WhichIsSmaller) If theDelegatedFunc(thePair(0), thePair(1)) = _ Comparison.theSecond Then Dim temp As Object = thePair(0) thePair(0) = thePair(1) thePair(1) = temp End If End Sub ' public method which orders the two objects ' by the reverse of whatever criteria the object likes! Public Sub ReverseSort(ByVal theDelegatedFunc As WhichIsSmaller) If theDelegatedFunc(thePair(0), thePair(1)) = _ Comparison.theFirst Then Dim temp As Object = thePair(0) thePair(0) = thePair(1) thePair(1) = temp End If End Sub ' ask the two objects to give their string value Public Overrides Function ToString( ) As String Return thePair(0).ToString( ) & ", " & thePair(1).ToString( ) End Function End Class Public Class Dog Private weight As Integer Public Shared ReadOnly OrderDogs As _ New Pair.WhichIsSmaller( _ AddressOf Dog.WhichDogIsSmaller) Public Sub New(ByVal weight As Integer) Me.weight = weight End Sub ' dogs are ordered by weight Public Shared Function WhichDogIsSmaller( _ ByVal o1 As Object, ByVal o2 As Object) As Comparison Dim d1 As Dog = DirectCast(o1, Dog) Dim d2 As Dog = DirectCast(o2, Dog) If d1.weight > d2.weight Then Return Comparison.theSecond Else Return Comparison.theFirst End If End Function Public Overrides Function ToString( ) As String Return weight.ToString( ) End Function End Class Public Class Student Private name As String Public Shared ReadOnly OrderStudents As _ New Pair.WhichIsSmaller( _ AddressOf Student.WhichStudentIsSmaller) Public Sub New(ByVal name As String) Me.name = name End Sub ' students are ordered alphabetically Public Shared Function WhichStudentIsSmaller( _ ByVal o1 As Object, ByVal o2 As Object) As Comparison Dim s1 As Student = DirectCast(o1, Student) Dim s2 As Student = DirectCast(o2, Student) If String.Compare(s1.name, s2.name) < 0 Then Return Comparison.theFirst Else Return Comparison.theSecond End If End Function Public Overrides Function ToString( ) As String Return name End Function End Class Class Tester Public Sub Run( ) ' create two students and two dogs ' and add them to Pair objects Dim Jesse As New Student("Jesse") Dim Stacey As New Student("Stacey") Dim Milo As New Dog(65) Dim Fred As New Dog(12) Dim studentPair As New Pair(Jesse, Stacey) Dim dogPair As New Pair(Milo, Fred) Console.WriteLine("studentPair: {0}", _ studentPair.ToString( )) Console.WriteLine("dogPair: {0}", _ dogPair.ToString( )) ' sort using the delegates studentPair.Sort(Student.OrderStudents) Console.WriteLine("After Sort studentPair: {0}", _ studentPair.ToString( )) studentPair.ReverseSort(Student.OrderStudents) Console.WriteLine("After ReverseSort studentPair: {0}", _ studentPair.ToString( )) dogPair.Sort(Dog.OrderDogs) Console.WriteLine("After Sort dogPair: {0}", _ dogPair.ToString( )) dogPair.ReverseSort(Dog.OrderDogs) Console.WriteLine("After ReverseSort dogPair: {0}", _ dogPair.ToString( )) End Sub Public Shared Sub Main( ) Dim t As New Tester( ) t.Run( ) End Sub End Class End Namespace
The output from this modified listing (Example 12-2) is identical to the output for Example 12-1.
The problem with shared delegates is that they must be instantiated�whether or not they are ever used�as with Student and Dog in the previous example. You can improve these classes by changing the shared delegate fields to properties.
For Student, take out the declaration:
Public Shared ReadOnly OrderStudents As _ New Pair.WhichIsSmaller( _ AddressOf Student.WhichStudentIsSmaller)
and replace it with the following:
Public Shared ReadOnly Property OrderStudents( ) _ As Pair.WhichIsSmaller Get Return New Pair.WhichIsSmaller( _ AddressOf WhichStudentIsSmaller) End Get End Property
Similarly, you replace the Dog shared field with:
Public Shared ReadOnly Property OrderDogs( ) _ As Pair.WhichIsSmaller Get Return New Pair.WhichIsSmaller( _ AddressOf WhichDogIsSmaller) End Get End Property
The assignment of the delegates is unchanged:
studentPair.Sort(Student.OrderStudents) dogPair.Sort(Dog.OrderDogs)
When the OrderStudent property is accessed, the delegate is created:
Return New Pair.WhichIsSmaller( _ AddressOf WhichStudentIsSmaller)
The key advantage is that the delegate is not created until it is requested. This allows the Tester class to determine when it needs a delegate but still allows the details of the creation of the delegate to be the responsibility of the Student (or Dog) class.
Top |