Friday, September 28, 2007

DN4DP#20: .NET only: Multi-cast events

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

Last time we're scared by unsafe code. Here we discuss the syntax for normal Delphi events vs .NET-style multicast events.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Multi-cast events

Delphi for .NET has full support for both old-style single cast events and .NET style multi cast events.

To write a traditional Delphi event, you first declare a delegate type using the procedure of object syntax, then declare an event property that reference a delegate field in the read and write specifiers.

type
TLevelChangedEvent = procedure (Sender: TObject; NewLevel: integer) of object;
TMyComponent = class
strict private
FOnLevelChanged: TLevelChangedEvent;
public
property OnLevelChanged: TLevelChangedEvent read FOnLevelChanged write FOnLevelChanged;
end;

This is a single-cast event that will compile both in .NET and Win32. It supports direct assignment of a method reference or nil to the event property and it supports directly invoking the event property. For instance, most VCL events are single cast and support assignments like this:

  MyComponent.OnLevelChanged := MyTest.FirstTarget;
MyComponent.OnLevelChanged(nil, 1);

However, many .NET consumers will expect multicast events in your classes. To enable this in a Delphi for .NET class you simply use add and remove specifiers instead of read and write, like this:

  TMyComponent = class
strict private
FOnMultiChanged: TLevelChangedEvent;
public
property OnMultiChanged: TLevelChangedEvent add FOnMultiChanged remove FOnMultiChanged;
end;

The simplest solution is simply to reference a delegate field as before - the compiler will then implement proper add_Event and remove_Event methods for you. In some special cases you may want to implement your own logic in these routines - to do that you simply write and reference your own add_Event and remove_Event methods, like this

  TMyComponent = class
strict private
FOnCustomChanged: TLevelChangedEvent;
public
procedure add_OnCustomChanged(Value: TLevelChangedEvent);
procedure remove_OnCustomChanged(Value: TLevelChangedEvent);
property OnCustomChanged: TLevelChangedEvent add add_OnCustomChanged remove remove_OnCustomChanged;
end;

procedure TMyComponent.add_OnCustomChanged(Value: TLevelChangedEvent);
var
Inlist: Delegate;
begin
if Assigned(FOnCustomChanged) then
for Inlist in Delegate(@FOnCustomChanged).GetInvocationList do
if InList.Equals(Delegate(@Value)) then
Exit;
FOnCustomChanged := TLevelChangedEvent(Delegate.Combine(Delegate(@FOnCustomChanged), Delegate(@Value)));
end;

procedure TMyComponent.remove_OnCustomChanged(Value: TLevelChangedEvent);
begin
FOnCustomChanged := TLevelChangedEvent(Delegate.Remove(Delegate(@FOnCustomChanged), Delegate(@Value)));
end;

This example add-handler only allows unique delegate targets, ignoring any attempt to add the same object’s method more than once. Note the tricky-looking code with casts to Delegate and use of the @-operator. The Delegate casts are required to force the compiler to treat the procedure of object as a System.Delegate instance (which is an implementation detail from the compiler’s point of view). The @-operator is required to prevent the compiler from trying to call the event instead of evaluating its value.

Most WinForms events are multicast events - they support multiple methods as targets. To add or remove a method from a multi-cast event, you use the Include and Exclude intrinsic procedures:

  Include(MyComponent.OnMultiChanged, MyTest.FirstTarget);
Include(MyComponent.OnMultiChanged, MyTest.SecondTarget);
MyComponent.TriggerMulti(6);
Exclude(MyComponent.OnMultiChanged, MyTest.FirstTarget);

This corresponds directly to the += and -= operators that C# supports on events.


Tip: Use multi-cast add/remove events for WinForms code and components. Use single-cast read/write events for VCL for .NET code and components, unless you really need multi-cast behavior. "


Update/Note: If you need to implement an interface that includes a multi-cast event property - i.e. the interface has add_Event and remove_Event methods - you can get by by declaring an add/remove event property on the class, with the add and remove specifiers referencing a procedure of object field. This will force the compiler to generate the add_Event and remove_Event methods for you - just like it did in the third code block above.

No comments:



Copyright © 2004-2007 by Hallvard Vassbotn