Xtreme Visual Basic Talk

Xtreme Visual Basic Talk (http://www.xtremevbtalk.com/)
-   Tutors' Corner (http://www.xtremevbtalk.com/tutors-corner/)
-   -   Delegates in VB.Net (http://www.xtremevbtalk.com/tutors-corner/159976-delegates-vb-net.html)

excaliber 04-11-2004 09:22 AM

Delegates in VB.Net
 
Introduction
Here is a quick look at the usage of Delegates. Delegates are extremely powerful, but at first glance don't seem all that useful. Anyhow, a Delegate is a type-safe object oriented function pointer.

What's that mean
Well, lets look at the "function pointer" part first. Every function (or subroutine, or whatever) is really only an address in memory. To execute the function, you just move to the address and begin. A delegate object is just a "mobile" container for the memory address. Now the "type-safe" part. In C language there is a Callback method that provides a function pointer. But because the function pointer contains nothing more than the memory address, it does not have the argument list either. This means you can get some nasty results when trying to use a wrong set of arguments. VB.Net's delegate objects are type-safe though, meaning they will error you provide them the wrong set of arguments.

Simple example
Code:
Class Delegate 'This is the delegate declaration. Each delegate object must 'have a declaration. The Delegate keyword specifies it as a delegate 'declaration Public Delegate Sub delegateDog Private Sub Example() 'Here we create a delegate object by declaring as 'delegateDog (from the delegate declaration above) Dim doDog as delegateDog 'set the object to the AddressOf the Size subroutine doDog = AddressOf Size 'Invoke (or execute) the delegate (which really just executes 'the subroutine) doDog.Invoke() End Sub 'This is the target subroutine that will be used for the delegate Private Sub Size() MsgBox("Big dog") End Sub End Class
When run, a message box will appear with the words "Big Dog". Nifty. We declared a Delegate subrountine (delegateDog), then created an object as one. We set the delegate object to the AddressOf (returns the type-safe memory pointer) of the Size subroutine. Then we invoked the delegate

The cool thing about delegates is their OO properties. You treat them just like any other objects. They can be passed around from class to class, set to other delegates, etc. Very powerful stuff. Having a hard time seeing what's so great? Here are three examples.

The first is going to go without code, as it would require alot of explanation that is unrelated to delegates. In a typical multithreaded environment, any thread that tries to update the parent thread's UI will have trouble. Things start to get flaky when that happens. Instead of directly accessing the UI, or passing a reference to the form, you can pass a delegate. The delegate is set to the UI update function, then passed as an argument to the new thread. The thread can then update the UI whenever it chooses. Why can it do this? Because it's accessing the parent thread's function (and thus in the thread space of the parent), to update the UI (which is perfectly fine).

This is also codeless, because it is more of an explanation. Delegates can be used to give access to any function from any class. Say I have a private function in mySuperClass. I can create a delegate of that function and pass it to myClass. Normally, even if a reference of the class was passed, that function could not be accessed. But because you have a delegate of the fucntion, you can execute it from myClass (despite not having adequate "permission" to do so).

Last example, and this one has code. Ever wonder how Events work? An event just uses Delegates! VB.Net hides some stuff from you to make the interface nicer, but it's the same thing. Let's make a simple app that sets your bank balance (with a slider) and displays your "wealth rating".

First, the bank class:
Code:
Public Class clsBank 'An enum for making our lives easier Public Enum eWealth FilthyRich Rich Modest Poor LivingInBox End Enum 'the delegate declaration, this time it has an argument that may 'be passed as well. The argument is an eWealth Enum Public Delegate Sub BankEventHandler(ByVal WealthRating As eWealth) 'Now we declare the actual delegate object Private doWealthRating as BankEventHandler 'A variable to hold the actual wealth value Private intWealth as Int32 'This function allows the calling class to pass the delegate in initially. 'The delegate object will be passed to us from the calling class so that 'we can manipulate it Public Sub PassDelegate(ByVal doDel as BankEventHandler) doWealthRating = doDel End Sub 'Property that we will be using to show how events are the same as events Public Property WealthRating() As Int32 Get Return intWealth End Get 'If the value set is less than zero, set it to zero Set(ByVal Value As Int32) If Value < 0 Then intWealth = 0 Else 'Otherwise, set the internal wealth variable to the passed value intWealth = Value ' And here set up a select case on the value 'Each different case we invoke the delegate object and pass 'one of the Enum values. This is essentially executing the subroutine 'in the calling class. Select Case intWealth Case is > 100 doWealthRating.Invoke(CartridgeState.FilthyRich) Case 60 To 99 doWealthRating.Invoke(CartridgeState.Rich) Case 30 To 59 doWealthRating.Invoke(CartridgeState.Modest) Case 1 To 29 doWealthRating.Invoke(CartridgeState.Poor) Case is = 0 doWealthRating.Invoke(CartridgeState.LiveInBox) End Select End If End Set End Property End Class
We have alot going on in this code. First we have an Enum to make our lives easier later on. Then we have the declaration of the delegate subroutine, and then the actual object for the delegate. There is a subroutine to pass in the delegate from the calling class, and a property for the wealth rating.

And now the class to use the previous code:
Code:
Public Class Form1 'A new instance of the bank class from the previous code Private oWealth as New clsBank 'On the form load, we want to pass the AddressOf (the type 'safe function pointer) to the new class instance. This is so 'the bank class can manipulate the delegate object. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load oWealth.PassDelegate(AddressOf WealthStatus) End Sub 'Scrollbar event. Here we use the public property of the bank 'to set the value. Private Sub scrBankWealth_Scroll(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles trInkLevel.Scroll oWealth.WealthRating = scrBankWealth.Value End Sub 'At the same time the scroller is moved, the delegate object in the other 'class will invoke this method (much like an event). It will also pass a 'BankEventHandler argument Private Sub WealthStatus(ByVal Status as clsBank.BankEventHandler) Dim Value as String 'A select case to display the value in a label Select Case Status Case clsBank.BankEventHandler.FilthyRich Value = "FilthyRich" Case clsBank.BankEventHandler.Rich Value = "Rich" Case clsBank.BankEventHandler.Modest Value = "Modest" Case clsBank.BankEventHandler.Poor Value = "Poor" Case clsBank.BankEventHandler.LiveInBox Value = "LiveInBox" End Select lblStatus = State End Sub End Class
Here, we have a new instance of the clsBank Class. On the load of the form, we pass a delegate to the class using AddressOf. Then there is the slider control's subroutine, which updates the class' wealth value. Then there is our delegate subroutine, WealthStatus, which displays the status in a label.

As you slide the slider, the label will change accordingly. Go back and look at the WealthRating property in clsBank. Notice how when it is set, it will invoke the delegate and pass an argument. Looking back at Form1, we see the delegate subroutine accepts the argument and deals with it by a select case and a label output.

Essentially, we just created a custom programmable Event and Event Handler. Pretty cool.

Conclusion
Delegate objects are extremely powerful, allowing you to access functions from anywhere in an object oriented manner. They can also be used to create custom events.

Deadalus 09-11-2004 04:06 AM

1 Attachment(s)
Something seems to have gone wrong with that last example Excaliber gave, apparently it mixes up two different examples. He's not around, so I'm taking the liberty of attaching a working version of that example project. Any errors in this one are completely my merit. :)

Deadalus 09-11-2004 05:19 AM

Multicast delegates
 
1 Attachment(s)
An interesting feature about delegates is that they can be "multicast", which essentially means that they can notify or trigger multiple methods.

Take the last example above and suppose that we want that a change in wealthrating not only changes the label, but triggers another, seperate procedure as well (here a simple procedure that changes the form's background color, named WealthColor). The Load procedure of our form would look something like this:
Code:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'set the delegate to the first procedure oWealth.PassDelegate(AddressOf WealthStatus) 'set it to the second procedure (as well) oWealth.PassDelegate(AddressOf WealthColor) End Sub
With our current PassDelegate procedure this won't work. Or rather, it will, but the second line will simply replace the pointer to WealthStatus in our delegate with a pointer to WealthColor.

So we need to change the PassDelegate sub in such a way that it adds a function pointer to the one present rather than replacing it. Luckily, the Delegate class has a method Combine that lets us do just that.
Code:
Public Sub PassDelegate(ByVal doDel As BankEventHandler) doWealthRating = CType([Delegate].Combine(doWealthRating, doDel), BankEventHandler) End Sub
(Side note: Don't let the square brackets confuse you. They're just there to indicate that we mean the class Delegate, and not the identically named keyword. To avoid this trickery, we could also have done this:
Code:
doWealthRating = CType(doWealthRating.Combine(doWealthRating, doDel), BankEventHandler)
where we use a Delegate object to call the Shared method Combine. I prefer not to do that, to make clear it's not an instance method we're using.)

So what's happening here? We simply take two delegates, with their own list of functions they notify, and return a new delegate, with a notification list that combines these lists. The CType is only there because Combine returns a Delegate object and we want to store it in a variable of our more specific, derived delegate type.

So now when we use PassDelegate repeatedly, like in the Load sub above, we're adding methods to our notification list. When Invoke is called, all methods in this list will execute.

Note that when the first method is passed, doWealthrating will still be Nothing. Combine is defined to then return the other passed delegate, so we don't even need to code for this special case.

It's easy to see that in our simple example, we could have just elaborated the one existing method. Or, for that matter, used a classic event. It's equally clear though, that this gives us a very elegant way to add or remove logically independent procedures that should be triggered by the same event. Moreover, the methods that a delegate triggers can be in different classes, as long as they have access to the same delegate object.

A more elaborate discussion of multicast delegates can be found here: http://msdn.microsoft.com/msdnmag/is...asicInstincts/


All times are GMT -6. The time now is 05:11 AM.

Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Search Engine Optimisation provided by DragonByte SEO v2.0.15 (Lite) - vBulletin Mods & Addons Copyright © 2017 DragonByte Technologies Ltd.
All site content is protected by the Digital Millenium Act of 1998. Copyright©2001-2011 MAS Media Inc. and Extreme Visual Basic Forum. All rights reserved.
You may not copy or reproduce any portion of this site without written consent.