delphiclosuresanonymous-methods

Assign an anonymous method to an interface variable or parameter?


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.


Solution

  • 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;