Home Home Archives About red box
Username:
Password:
 
rss

Filed Under Delphi
digg Multicast Delegates
visits: 785 | score: 5 
posted by sysrpl on Wednesday December 29, 2010 10:50 AM

ere is a new implementation of multicast delegates for Delphi based on generics. In this article I provide a brief summary of my multicast delegate implementation. At the end of the article I've included a download to the complete implementation source code.

For those unfamiliar with the concept of multicast delegates, they are constructs, popularized by C#, where multiple event handlers can be added or removed to an event property. Although similar to Delphi event types, the difference is that assigning a Delphi event causes the previously stored event to be overwritten. As such, Delphi events may be described as single cast delegates.

My Delphi multicast delegate implementation is identified as TDelegate<T>. It may be added to the definitions of a class like so:

type
  TSomeForm = class(TForm)
  private
    FOnChange: TDelegate<TNotifyEvent>;
  end;

The type TDelegate<TNotifyEvent> does not need to be created, destroyed, or otherwise managed. It is a simple generic holder for a collection of event types.

To add an event to FOnChange in the example above you could write:

begin
  FOnChange.Add(ChangeHandlerOne);
  FOnChange.Add(ChangeHandlerTwo);
end;

Note that FOnChange was never initialized. Also note that multiple event handlers can be added to FOnChange.

A handler continuing with the example could look like this:

procedure TSomeForm.ChangeHandlerOne(Sender: TObject);
begin
  ShowMessage('Change event handler one fired');
end;

Then, to invoke the list of events you may do the following:

procedure TSomeForm.DoChange;
var
  Event: TNotifyEvent;
begin
  for Event in FOnChange do Event(Self);
end;

Invoking multicast events n this fashion is different than typical Delphi event invocation. A benefit to this new syntax above is that it is longer necessary to check if FOnChange is assigned before invocation.

Another feature of my multicast delegate implementation is that programmers now have the option to restrict event invocation to a private section of their class. This is feature is available through a generic interface called IDelegate<T>:

type
  IDelegate<T> = interface
    procedure Add(const Handler: T);
    procedure Remove(const Handler: T);
  end;

Using this interface you can add and remove event handlers to event consumers while denying event invocation:

...
  private
    FOnChange: TDelegate<TNotifyEvent>;
    function GetOnChange: IDelegate<TNotifyEvent>;
  public 
    property OnChange: IDelegate<TNotifyEvent> read GetOnChange;
...
  
function TSomeForm.GetOnChange: IDelegate<TNotifyEvent>;
begin
  Result := FOnChange;
end;

Simplifying the example further, multicast delegates may be derived to define non generic types:

type
  TNotifyDelegate = TDelegate<TNotifyEvent>;
  INotifyDelegate = IDelegate<TNotifyEvent>;

In summary, the example can be put together by writing the following:

type
  TSomeForm = class(TForm)
  private
    FOnChange: TNotifyDelegate;
    function GetOnChange: INotifyDelegate;
  protected
    procedure DoChange; virtual;
  public
    property OnChange: INotifyDelegate read GetOnChange;
  end;
 
implementation
 
procedure TSomeForm.DoChange;
var
  Event: TNotifyEvent;
begin
  for Event in FOnChange do Event(Self);
end;
 
function TSomeForm.GetOnChange: INotifyDelegate;
begin
  Result := FOnChange;
end;

External code can then add or remove event handlers like so:

begin
  // Add OnChange handler
  SomeForm.OnChange.Add(SomeFormChanged);
  // Remove OnChange handler
  SomeForm.OnChange.Remove(SomeFormChanged);
end;

Here is the source code to my implementation of multicast delegates. You are free to copy, modify, or otherwise distribute it.

unit Delegates;

interface

uses
  Generics.Collections;

{ IDelegate<T> }

type
  IDelegate<T> = interface
    ['{ADBC29C1-4F3D-4E4C-9A79-C805E8B9BD92}']
    procedure Add(const Handler: T);
    procedure Remove(const Handler: T);
  end;

{ IDelegateContainer<T> }

  IDelegateContainer<T> = interface
    ['{ED255F00-3112-4315-9E25-3C1B3064C932}']
    function GetDelegate: IDelegate<T> ;
    function GetEnumerator: TEnumerator<T>;
    property Delegate: IDelegate<T> read GetDelegate;
    property Enumerator: TEnumerator<T> read GetEnumerator;
  end;

{ TDelegateImpl<T> }

  TDelegateImpl<T> = class(TInterfacedObject, IDelegate<T>)
  private
    FList: TList<T>;
  protected
    { IDelegate<T> }
    procedure Add(const Event: T);
    procedure Remove(const Event: T);
  public
    constructor Create(List: TList<T>);
  end;

{ TDelegateContainerImpl<T> }

  TDelegateContainerImpl<T> = class(TInterfacedObject, IDelegateContainer<T>)
  private
    FDelegate: IDelegate<T>;
    FList: TList<T>;
  protected
    { IDelegateContainer<T> }
    function GetDelegate: IDelegate<T>;
    function GetEnumerator: TEnumerator<T>;
  public
    destructor Destroy; override;
  end;

{ TDelegate<T> }

  TDelegate<T> = record
  private
    FContainer: IDelegateContainer<T>;
    function GetContainer: IDelegateContainer<T>;
  public
    class operator Implicit(var Delegate: TDelegate<T>): IDelegate<T>;
    function GetEnumerator: TEnumerator<T>;
    procedure Add(const Handler: T);
    procedure Remove(const Handler: T);
  end;

{ Example usage:

  TNotifyDelegate = TDelegate<TNotifyEvent>;
  INotifyDelegate = IDelegate<TNotifyEvent>; }

implementation

{ TDelegateImpl<T> }

constructor TDelegateImpl<T>.Create(List: TList<T>);
begin
  inherited Create;
  FList := List;
end;

{ TDelegateImpl<T>.IDelegate<T> }

procedure TDelegateImpl<T>.Add(const Event: T);
begin
  if FList.IndexOf(Event) < 0 then
    FList.Add(Event);
end;

procedure TDelegateImpl<T>.Remove(const Event: T);
begin
  if FList.IndexOf(Event) > -1 then
    FList.Remove(Event);
end;

{ TDelegateContainerImpl<T> }

destructor TDelegateContainerImpl<T>.Destroy;
begin
  FList.Free;
  inherited Destroy;
end;

{ TDelegateContainerImpl<T>.IDelegateContainer<T> }

function TDelegateContainerImpl<T>.GetDelegate: IDelegate<T>;
begin
  if FList = nil then
    FList := TList<T>.Create;
  if FDelegate = nil then
    FDelegate := TDelegateImpl<T>.Create(FList);
  Result := FDelegate;
end;

function TDelegateContainerImpl<T>.GetEnumerator: TEnumerator<T>;
begin
  if FList = nil then
    FList := TList<T>.Create;
  Result := FList.GetEnumerator;
end;

{ TDelegate<T> }

class operator TDelegate<T>.Implicit(var Delegate: TDelegate<T>): IDelegate<T>;
begin
  Result := Delegate.GetContainer.Delegate;
end;

function TDelegate<T>.GetContainer: IDelegateContainer<T>;
begin
  if FContainer = nil then
    FContainer := TDelegateContainerImpl<T>.Create;
  Result := FContainer;
end;

function TDelegate<T>.GetEnumerator: TEnumerator<T>;
begin
  Result := GetContainer.GetEnumerator;
end;

procedure TDelegate<T>.Add(const Handler: T);
begin
  GetContainer.Delegate.Add(Handler);
end;

procedure TDelegate<T>.Remove(const Handler: T);
begin
  GetContainer.Delegate.Remove(Handler);
end;

end.


print send topic Rate this article  

Registering multiple events fail

See question at StackOverflow : http://stackoverflow.com/questions/7428244/is-tlistt-indexof-using-fcomparer-compare-correct
reply quote
perm
mx4399 said on Thursday September 15, 2011 5:41 AM

Work around and tests

https://bitbucket.org/MX4399/multicast-events-using-generics
reply quote
perm
mx4399 said on Friday September 16, 2011 6:32 AM

Re: Work around and tests

In my library I actually don't use the Delphi generics implementation, rather I have my own generic list and other generic routines. As noted, the bug lays in Embarcadero's generic implementation.
reply quote
perm
sysrpl said on Monday October 3, 2011 3:27 PM

Title:

image link indent align right align middle align left quote underline bold code quote
Comment:

page generated in 0.156 seconds | last modified 2/06/2011 8:35 PM
none  none