Anonymous methods are essentially interface
s 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'
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 TObject
s. 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.
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With