Assign an anonymous method to an interface variable or parameter?
Asked Answered
P

3

15

Anonymous methods are essentially interfaces with an Invoke method:

type
  TProc = reference to procedure;

  IProc = interface
    procedure Invoke;
  end;

Now, is there a possibility to assign them to an actual interface variable or pass them as interface parameter?

procedure TakeInterface(const Value: IInterface);
begin
end;

var
  P: TProc;
  I: IInterface;
begin
  I := P; // E2010
  TakeInterface(P); // E2010
end;

[DCC32 Error] E2010 Incompatible types: 'IInterface' and 'procedure, untyped pointer or untyped parameter'

Question: What would be the use case for this?

There are a lot of objects out there, that cannot be simply kept alive with an interface reference. Therefore they are wrapped in a closure and get destroyed with it, "Smart Pointers":

type
  I<T> = reference to function : T;

  TInterfaced<T: class> = class (TInterfacedObject, I<T>)
  strict private
    FValue: T;
    function Invoke: T; // Result := FValue;
  public
    constructor Create(const Value: T); // FValue := Value;
    destructor Destroy; override; // FValue.Free;
  end;

  IInterfacedDictionary<TKey, TValue> = interface (I<TDictionary<TKey, TValue>>) end;

  TKey = String;
  TValue = String;

var
  Dictionary: IInterfacedDictionary<TKey, TValue>;
begin
  Dictionary := TInterfaced<TDictionary<TKey, TValue>>
    .Create(TDictionary<TKey, TValue>.Create);
  Dictionary.Add('Monday', 'Montag');
end; // FRefCount = 0, closure with object is destroyed

Now, sometimes it is necessary to not only keep one single object alive but also a context with it. Imagine you have a TDictionary<TKey, TValue> and you pull an enumerator out of it: TEnumerator<TKey>, TEnumerator<TValue> or TEnumerator<TPair<TKey, TValue>>. Or the dictionary contains and owns TObjects. Then both, the new object and the dictionary's closure would go into to a new closure, in order to create one single, standalone reference:

type
  TInterfaced<IContext: IInterface; T: class> = class (TInterfacedObject, I<T>)
  strict private
    FContext: IContext;
    FValue: T;
    FFreeObject: Boolean;
    function Invoke: T; // Result := FValue;
  public
    constructor Create(const Context: IContext; const Value: T; const FreeObject: Boolean = True); // FValue = Value; FFreeObject := FreeObject;
    destructor Destroy; override; // if FFreeObject then FValue.Free;
  end;

  IInterfacedEnumerator<T> = interface (I<TEnumrator<T>>) end;

  TValue = TObject; // 

var
  Dictionary: IInterfacedDictionary<TKey, TValue>;
  Enumerator: IInterfacedEnumerator<TKey>;
  Obj: I<TObject>;
begin
  Dictionary := TInterfaced<TDictionary<TKey, TValue>>
    .Create(TObjectDictionary<TKey, TValue>.Create([doOwnsValues]));
  Dictionary.Add('Monday', TObject.Create);

  Enumerator := TInterfaced<
    IInterfacedDictionary<TKey, TValue>,
    TEnumerator<TKey>
  >.Create(Dictionary, Dictionary.Keys.GetEnumerator);

  Obj := TInterfaced<
    IInterfacedDictionary<TKey, TValue>,
    TObject
  >.Create(Dictionary, Dictionary['Monday'], False);

  Dictionary := nil; // closure with object still held alive by Enumerator and Obj.
end;

Now the idea is to melt TInterfaced<T> and TInterfaced<IContext, T>, which would make the type parameter for the context obsolete (an interface is enough) and result in these consturctors:

constructor TInterfaced<T: class>.Create(const Value: T; const FreeObject: Boolean = True); overload;
constructor TInterfaced<T: class>.Create(const Context: IInterface; const Value: T; const FreeObject: Boolean = True); overload;

Being a (pure) closure might not be the primary use one would think of when working with anonymous methods. However, their types can be given as an interface of a class whose objects can do cleanup on a closure's destruction, and a TFunc<T> makes it a fluent access to its content. Though, they don't share a common ancestor and it seems values of reference to types cannot be assigned to interface types, which means, there is no unified, safe and futureproof way to refer to all types of closures to keep them alive.

Plosion answered 8/7, 2013 at 18:19 Comment(2)
What would be the use case for this? Secondly anonymous methods are not essentially interfaces. They are implemented as interfaces but that's pure interface detail.Dicrotic
Thanks for adding some explanation and commentary. I must say that I still don't really understand where you are going with this. I cannot understand the benefit of obtaining the IInterface reference for the closure, over just holding the method reference. However, it was interesting to learn that you can write class(TInterfacedObject, TProc) which is definitely news to me!Dicrotic
A
12

This is super easy. I will show you two ways.

var
  P: TProc;
  I: IInterface;
begin
  I := IInterface(Pointer(@P)^);
  TakeInterface(I);
end;

Another way is to declare PInterface

type
  PInterface = ^IInterface;
var
  P: TProc;
  I: IInterface;
begin
  I := PInterface(@P)^;
  TakeInterface(I);
end;
Alliaceous answered 25/3, 2014 at 20:11 Comment(2)
What does TakeInterface do? I can't find documentation for it anywhere.Olympie
Silly me, I figured the TakeInterface was a synonym for _AddRef, because it does not look like casting the TProc will increase its refcount; thus causing I to be destroyed prematurely.Olympie
D
6

To the best of my knowledge you cannot do what you need with casting.

You can, I suppose, use Move to make an assignment:

{$APPTYPE CONSOLE}
type
  TProc = reference to procedure(const s: string);
  IProc = interface
    procedure Invoke(const s: string);
  end;

procedure Proc(const s: string);
begin
  Writeln(s);
end;

var
  P: TProc;
  I: IProc;

begin
  P := Proc;
  Move(P, I, SizeOf(I));
  I._AddRef;//explicitly take a reference since the compiler cannot do so
  I.Invoke('Foo');
end.

I've honestly no idea how robust this is. Will it work on multiple Delphi versions? Is it wise to rely on obscure undocumented implementation details? Only you can determine whether the gains you make outweigh the negatives of relying on implementation details.

Dicrotic answered 8/7, 2013 at 18:54 Comment(0)
C
0

The easiest way to cast is the folowing:

IProc((@P)^)
Chatterton answered 16/6, 2022 at 7:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.