Delegates

Team-Fly    

 
Application Development Using Visual Basic and .NET
By Robert J. Oberg, Peter Thorsteinson, Dana L. Wyatt
Table of Contents
Chapter 6.  VB.NET in the .NET Framework


Interfaces facilitate writing code so that your program can be called into by some other code. This style of programming has been available for a long time, under the guise of "callback" functions. In this section we examine delegates in VB.NET, which can be thought of as type-safe and object-oriented callback functions. Delegates are the foundation for a design pattern, known as events , which we'll look at in the next section.

A callback function is one which your program specifies and "registers" in some way, and which then gets called by other code. In C and C++ callback functions are implemented using function pointers.

In VB.NET you can encapsulate a reference to a method as a delegate object. A delegate can refer to either a static method or an instance method. When a delegate refers to an instance method, it stores both an object instance and an entry point to the instance method. The instance method can then be called through this object instance. When a delegate object refers to a static method, it stores just the entry point of this static method.

You can pass this delegate object to other code, which can then call your method. The code that calls your delegate method does not have to know at compile time which method is going to be called at runtime.

In VB.NET a delegate is considered a reference type that is similar to a class type. A new delegate instance is created just like any other class instance, using the New operator. In fact, VB.NET delegates are implemented by the .NET Framework class library as a class, derived ultimately from System.Delegate .

Delegates are object oriented and type safe, and they enjoy the safety of the managed code execution environment.

Declaring a Delegate

You declare a delegate in VB.NET using a special notation with the keyword Delegate and the signature of the encapsulated method. A naming convention suggests that your name should end with "Callback."

We illustrate delegates in the sample program DelegateAccount . Here is an example of a delegate declaration from the file DelegateAccount.vb . The name NotifyCallback is arbitrary, but note that it follows the convention of ending with "Callback."

 graphics/codeexample.gif Public Delegate Sub NotifyCallback(_  ByVal balance As Decimal) 

Defining a Method

When you instantiate a delegate, you will need to specify a method, which must match the signature in the delegate declaration. The method may be either a static method or an instance method. Here are some examples of methods that can be hooked to the NotifyCallback delegate:

  Private Shared Sub NotifyCustomer(ByVal balance As Decimal)  Console.WriteLine("Dear customer,")    Console.WriteLine(_       "   Account overdrawn, balance = {0}", balance) End Sub  Private Shared Sub NotifyBank  (  ByVal balance As Decimal)  Console.WriteLine("Dear bank,")    Console.WriteLine(_       "   Account overdrawn, balance = {0}", balance) End Sub  Private Sub NotifyInstance  (  ByVal balance As Decimal)  Console.WriteLine("Dear instance,")    Console.WriteLine(_       "   Account overdrawn, balance = {0}", balance) End Sub 

Creating a Delegate Object

You instantiate a delegate object with the New operator, just as you would with any other class. The following code illustrates creating two delegate objects. The first delegate variable, named custDlg , is associated with a static method named NotifyCustomer . The second one, named instDlg , is associated with an instance method named NotifyInstance . The second delegate object internally will store both a method entry point and an object instance that is used for invoking the method.

 Dim  custDlg  As NotifyCallback = _    New NotifyCallback(AddressOf  NotifyCustomer  ) ... Dim da As DelegateAccount = New DelegateAccount() Dim  instDlg  As NotifyCallback = _    New NotifyCallback(AddressOf  da.NotifyInstance  ) 

Calling a Delegate

You "call" a delegate just as you would a method. The delegate object is not a method, but it has an encapsulated method. The delegate object "delegates" the call to this encapsulated method, hence the name "delegate." In the following code the delegate object m_notifyDlg is called whenever a negative balance occurs on a withdrawal. In this example the m_notifyDlg delegate object is initialized in the method SetDelegate .

  Private m_notifyDlg As NotifyCallback  ... Public Sub  SetDelegate  (ByVal dlg As NotifyCallback)  m_notifyDlg = dlg  End Sub ... Public Sub  Withdraw  (ByVal amount As Decimal)    m_balance -= amount    If m_balance < 0 Then  m_notifyDlg(Balance) '  call the delegate    End If End Sub 

Combining Delegate Objects

A powerful feature of delegates is that you can combine them. Delegates are "multicast," in which they have an invocation list of methods. When such a delegate is called, all the methods on the invocation list will be called in the order they appear in the invocation list. The Combine method of the Delegate class can be used to combine the invocation methods of two delegate objects. The Remove method of the Delegate class can be used to remove methods from the invocation list.

 Dim custDlg As NotifyCallback = _    New NotifyCallback(AddressOf NotifyCustomer) Dim bankDlg As NotifyCallback = _    New NotifyCallback(AddressOf NotifyBank) Dim currDlg As NotifyCallback = _  NotifyCallback.Combine  (custDlg, bankDlg) ... currDlg =  NotifyCallback.Remove  (currDlg, bankDlg) 

In this example we construct two delegate objects, each with an associated method. We then create a new delegate object whose invocation list will consist of both the methods NotifyCustomer and NotifyBank . When curr-Dlg is called, these two methods will be invoked. Later on in the code we remove the bankDlg delegate. Once this is done, the NotifyBank method is no longer in the delegate's invocation list, and the next time currDlg is called, only NotifyCustomer will be invoked.

Complete Example

The program DelegateAccount illustrates using delegates in our bank account scenario. The file DelegateAccount.vb declares the delegate NotifyCallback . The class DelegateAccount contains methods matching the signature of the delegate. The Main method instantiates delegate objects and combines them in various ways. The delegate objects are passed to the Account class, which uses its encapsulated delegate object to invoke suitable notifications when the account is overdrawn.

Observe how this structure is dynamic and loosely coupled . The Account class does not know or care which notification methods will be invoked in the case of an overdraft. It simply calls the delegate, which in turn calls all the methods on its invocation list. These methods can be adjusted at runtime.

Here is the code for the Account class:

 graphics/codeexample.gif ' Account.vb Public Class Account    Private m_balance As Decimal    Private m_notifyDlg As NotifyCallback    Public Sub New(_     ByVal balance As Decimal, _     ByVal dlg As NotifyCallback)       m_balance = balance       m_notifyDlg = dlg    End Sub    Public Sub SetDelegate(ByVal dlg As NotifyCallback)       m_notifyDlg = dlg    End Sub    Public Sub Deposit(ByVal amount As Decimal)        m_balance += amount    End Sub    Public Sub Withdraw(ByVal amount As Decimal)       m_balance -= amount       If m_balance < 0 Then          m_notifyDlg(Balance) 'call the delegate       End If    End Sub    Public ReadOnly Property Balance() As Decimal       Get          Return m_balance       End Get    End Property End Class 

Here is the code declaring and testing the delegate:

 ' DelegateAccount.vb Imports System Public Delegate Sub NotifyCallback(_  ByVal balance As Decimal) Class DelegateAccount 'Note: This is a Class, not a Module    Shared Sub Main()       Dim custDlg As NotifyCallback = _          New NotifyCallback(AddressOf NotifyCustomer)       Dim bankDlg As NotifyCallback = _          New NotifyCallback(AddressOf NotifyBank)       Dim currDlg As NotifyCallback = _          NotifyCallback.Combine(custDlg, bankDlg)       Dim acc As Account = New Account(100, currDlg)       Console.WriteLine("balance = {0}", acc.Balance)       acc.Withdraw(125)       Console.WriteLine("balance = {0}", acc.Balance)       acc.Deposit(200)       acc.Withdraw(125)       Console.WriteLine("balance = {0}", acc.Balance)       currDlg = NotifyCallback.Remove(currDlg, bankDlg)       acc.SetDelegate(currDlg)       acc.Withdraw(125)       Dim da As DelegateAccount = New DelegateAccount()       Dim instDlg As NotifyCallback = _          New NotifyCallback(AddressOf da.NotifyInstance)       currDlg = NotifyCallback.Combine(currDlg, instDlg)       acc.SetDelegate(currDlg)       acc.Withdraw(125)    End Sub    Private Shared Sub NotifyCustomer(_     ByVal balance As Decimal)       Console.WriteLine("Dear customer,")       Console.WriteLine(_          "   Account overdrawn, balance = {0}", balance)    End Sub    Private Shared Sub NotifyBank(_     ByVal balance As Decimal)       Console.WriteLine("Dear bank,")       Console.WriteLine(_          "   Account overdrawn, balance = {0}", balance)    End Sub    Private Sub NotifyInstance(_     ByVal balance As Decimal)       Console.WriteLine("Dear instance,")       Console.WriteLine(_          "   Account overdrawn, balance = {0}", balance)    End Sub End Class 

Here is the output from running the program. Notice which notification methods get invoked, depending upon the operations that have been performed on the current delegate object.

 balance = 100 Dear customer,    Account overdrawn, balance = -25 Dear bank,    Account overdrawn, balance = -25 balance = -25 balance = 50 Dear customer,    Account overdrawn, balance = -75 Dear customer,    Account overdrawn, balance = -200 Dear instance,    Account overdrawn, balance = -200 

Stock Market Simulation

As a further illustration of the use of delegates, consider the simple stock-market simulation, implemented in the directory StockMarket . The simulation consists of two modules:

  • The Admin module provides a user interface for configuring and running the simulation. It also implements operations called by the simulation engine.

  • The Engine module is the simulation engine. It maintains an internal clock and invokes randomly generated operations, based on the configuration parameters passed to it.

Figure 6-3 shows the high-level architecture of the simulation.

Figure 6-3. Architecture of stock-market simulation.

graphics/06fig03.gif

The following operations are available:

  • PrintTick: shows each clock tick.

  • PrintTrade: shows each trade.

The following configuration parameters can be specified:

  • Ticks on/off

  • Trades on/off

  • Count of how many ticks to run the simulation

Running the Simulation

Build and run the example program in StockMarket . Start with the default configuration: Ticks are OFF, Trades are ON, Run count is 100. (Note that the results are random and will be different each time you run the program.) If you enter the command run , then the output shows columns of data for clock tick, stock, price, and volume.

 graphics/codeexample.gif Ticks are OFF Trades are ON Run count = 100 Enter command, quit to exit : run    2  ACME    23   600   27  MSFT    63   400   27  IBM    114   600   38  MSFT    69   400   53  MSFT    75   900   62  INTC    27   800   64  MSFT    82   200   68  MSFT    90   300   81  MSFT    81   600   83  INTC    30   800   91  MSFT    73   700   99  IBM    119   400 : 

The available commands are listed when you type help at the colon prompt. The commands are

 count    set run count ticks    toggle ticks trades   toggle trades config   show configuration run      run the simulation quit     exit the program 
Delegate Code

Two delegates are declared in the Admin.vb file.

 Public Delegate Sub TickCallback(ByVal ticks As Integer) Public Delegate Sub TradeCallback(_  ByVal ticks As Integer, _  ByVal stock As String, _  ByVal price As Integer, _  ByVal volume As Integer) 

As we saw in the previous section, a delegate is similar to a class, and a delegate object is instantiated by New .

 Dim tickDlg As TickCallback = _    New TickCallback(AddressOf PrintTick) Dim tradeDlg As TradeCallback = _    New TradeCallback(AddressOf PrintTrade) 

A method is passed as the parameter to the delegate constructor. The method signature must match that of the delegate.

 Public Sub PrintTick(ByVal ticks As Integer)    Console.Write("{0} ", ticks)    If (++printcount = LINECOUNT) Then       Console.WriteLine()       printcount = 0    End If End Sub 
Passing the Delegates to the Engine

The Admin class passes the delegates to the Engine class in the constructor of the Engine class.

 Dim engine As Engine = New Engine(tickDlg, tradeDlg) 
Random-Number Generation

The heart of the simulation is the Run method of the Engine class. At the core of the Run method is assigning simulated data based on random numbers . We use the System.Random class, which we discussed in Chapter 4 in connection with the ArrayDemo program.

 While (i < stocks.Length)    Dim r As Double =  rangen.NextDouble()  If (r < tradeProb(i)) Then       Dim delta As Integer = _          price(i) * volatility(i)       If (  rangen.NextDouble()  < 0.5) Then          delta = -delta       End If       price(i) += delta       Dim volume As Integer = _  rangen.Next(minVolume, maxVolume)  * 100       tradeOp(_          tick, stocks(i), _          price(i), volume)    End If    i += 1 End While 
Using the Delegates

In the Engine class, delegate references are declared:

 Dim tickOp As TickCallback Dim tradeOp As TradeCallback 

The delegate references are initialized in the Engine constructor:

 Public Sub New(_   ByVal tickOp As TickCallback, _   ByVal tradeOp As TradeCallback)    Me.tickOp = tickOp    Me.tradeOp = tradeOp End Sub 

The method that is wrapped by the delegate object can then be called through the delegate reference:

 If showTicks Then     tickOp(tick) 

Team-Fly    
Top
 


Application Development Using Visual BasicR and .NET
Application Development Using Visual BasicR and .NET
ISBN: N/A
EAN: N/A
Year: 2002
Pages: 190

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net