Johan
Johan

Reputation: 76567

Why can't I declare a generic anonymous method outside a method body?

Edit, clarified the question, because I simplified it too much, thereby eliminating the problem I was actually facing

I have a delegate with a long implementation in the body.
For that reason I don't want to declare it inside the function where I'm using it.

type
  TTaskDelegate<A, B> = reference to procedure(const Data: IData);
  //-----------^^^^^^ note the type parameters here
  //-But no type parameters here---------------------------^^^^^^

The delegate is declared like the so that I can store it in a record that looks like:

TMultiDelegate = record
  strict  private
    fAA: TTaskDelegate<TOmniValue, TOmniValue>;
    fAB: TTaskDelegate<TOmniValue, IOmniBlockingCollection>;
    fBA: TTaskDelegate<IOmniBlockingCollection, TOmniValue>;
    fBB: TTaskDelegate<IOmniBlockingCollection, IOmniBlockingCollection>;
    fSimple: TSimpleTaskDelegate;
    fOutputCount: Integer;
    function GetDelegateType: TDelegateType;
  public
    constructor Init(AA: TTaskDelegate<TOmniValue, TOmniValue>; const OutputCount: integer = 1); overload;
    constructor Init(AB: TTaskDelegate<TOmniValue, IOmniBlockingCollection>;          const OutputCount: integer = 1); overload;
    .....

The type parameters also serve as a reminder to the implementor of the generic procedure what the input and output types are.

Because the type parameters are not repeated in the rest of the method header, they have to be retained when declaring the function.
For that reason Stefan's answer does not work.

Just declaring it as a unit constant (or unit variable) does not work.
Declaring it as a unit procedure will also not work, because of its generic signature.
The following code does not compile:

Sample A

const
  Test: integer = 0;

const
  DoesNotCompile: TTaskDelegate<TOmniValue, TOmniValue> =  
    procedure(const Data: IData)
    begin
      //Do stuff    
    end;

E2026 Constant expression expected

 //This variant will not compile either.
 procedure DoStuff<TOmniValue, TOmniValue>(const Data: IData)
 begin
   //DoStuff
 end;

When I wrap it in a function it does work.

Sample B

function ListSplitterDelegate: TTaskDelegate<TOmniValue, TOmniValue>;
begin
  Result:=
    procedure(const Data: IData)
    begin
      //Do stuff    
    end;
end;

It feels a bit superfluous to do it this way.
Is there a way to avoid having to wrap the generic anonymous function inside another function?

Upvotes: 1

Views: 1130

Answers (2)

J...
J...

Reputation: 31403

What you are trying to do is not possible. Anonymous methods are stateful, reference counted objects that are not constant by nature. Assignment of an anonymous method produces a closure whose state is continually inspected and modified as it its environment changes. It is in some ways like other compiler managed types, like dynamic arrays, which for somewhat similar reasons also cannot be a const.

Your solution of creating the ListSplitterDelegate function is probably the best you can do. Otherwise, you would need to declare DoesNotCompile as a variable and assign it at runtime.

var 
  CompilesOk : TTaskDelegate<TOmniValue, TOmniValue>;

  //...

initialization 
  CompilesOk := procedure(const Data: IData<TOmniValue, TOmniValue>)
                begin
                  //Do stuff    
                end;

Obviously, this has the problem that CompilesOk can be overwritten (and is otherwise a bad idea for a number of reasons). The ListSplitterDelegate is the best solution if you need this to be an anonymous method. I don't think you need this to be an anonymous method, however, since it is possible to assign a regular method to a reference to type.

SSCCE to demonstrate (using your updated code example and signatures):

unit Unit1;

interface

type
  IData = interface
  end;
  TOmniValue = record
  end;
  TTaskDelegate<A,B> = reference to procedure(const Data: IData);

  TMultiDelegate = record
  strict  private
    fAA: TTaskDelegate<TOmniValue, TOmniValue>;
  public
    constructor Init(AB: TTaskDelegate<TOmniValue, TOmniValue>);
    procedure DoIt;
  end;

implementation

constructor TMultiDelegate.Init(AB: TTaskDelegate<TOmniValue, TOmniValue>);
begin
  fAA := AB;
end;

procedure TMultiDelegate.DoIt;
var dat: IData;
begin
  fAA(dat);
end;

end.

Main :

program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  Unit1 in 'Unit1.pas';

procedure DoSomething(const Data : IData);
begin
  writeLn('foo');
end;

var
  tmd : TMultiDelegate;
begin
  tmd.Init(DoSomething);
  tmd.DoIt;  
  ReadLn;
end.

Compiles fine. Operates as expected. Tested in Delphi XE2.

Upvotes: 0

Stefan Glienke
Stefan Glienke

Reputation: 21713

Updated to the edited question:

Declaring it as regular procedure should work just fine:

procedure Whatever_TOmniValue_TOmniValue(const Data: IData);
begin
  //Do stuff    
end;

Upvotes: 2

Related Questions