Назначить анонимный метод переменной или параметру интерфейса?

Анонимные методы - это, по сути, interfaces с Invoke методом:

type
  TProc = reference to procedure;

  IProc = interface
    procedure Invoke;
  end;

Теперь, есть ли возможность назначить их реальной переменной интерфейса или передать их как параметр интерфейса?

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

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

[Ошибка DCC32] E2010 Несовместимые типы: 'IInterface' и 'процедура, нетипизированный указатель или нетипизированный параметр'

Вопрос: каков будет вариант использования для этого?

Существует множество объектов, которые нельзя просто поддерживать с помощью ссылки на интерфейс. Поэтому они обертываются закрытием и уничтожаются вместе с ним, "Умные указатели" < / а>:

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

Теперь иногда необходимо поддерживать не только один-единственный объект, но и контекст с ним. Представьте, что у вас есть TDictionary<TKey, TValue>, и вы извлекаете из него счетчик: TEnumerator<TKey>, TEnumerator<TValue> или TEnumerator<TPair<TKey, TValue>>. Или словарь содержит и владеет TObjects. Тогда и новый объект, и закрытие словаря перейдут в новое закрытие, чтобы создать одну отдельную отдельную ссылку:

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;

Теперь идея состоит в том, чтобы объединить TInterfaced<T> и TInterfaced<IContext, T>, что сделало бы параметр типа для контекста устаревшим (интерфейса достаточно) и в результате получились бы следующие конструкторы:

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;

Быть (чистым) закрытием, возможно, не в первую очередь при работе с анонимными методами. Однако их типы могут быть заданы как интерфейс класса, объекты которого могут выполнять очистку при разрушении замыкания, а TFunc<T> обеспечивает свободный доступ к его содержимому. Хотя у них нет общего предка, и кажется, что значения reference to типов не могут быть присвоены типам интерфейсов, а это означает, что не существует единого, безопасного и надежного способа ссылаться на все типы замыканий, чтобы поддерживать их жизнь.


person Max    schedule 08.07.2013    source источник
comment
Какой вариант использования для этого? Во-вторых, анонимные методы по сути не являются интерфейсами. Они реализованы как интерфейсы, но это чистая деталь интерфейса.   -  person David Heffernan    schedule 08.07.2013
comment
Спасибо за добавление некоторых пояснений и комментариев. Должен сказать, что до сих пор не очень понимаю, к чему вы клоните. Я не могу понять преимущества получения ссылки IInterface для закрытия по сравнению с простой ссылкой на метод. Однако было интересно узнать, что вы можете писать class(TInterfacedObject, TProc), что определенно является для меня новостью!   -  person David Heffernan    schedule 09.07.2013


Ответы (2)


Это очень просто. Я покажу вам два пути.

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

Другой способ - объявить PInterface

type
  PInterface = ^IInterface;
var
  P: TProc;
  I: IInterface;
begin
  I := PInterface(@P)^;
  TakeInterface(I);
end;
person Stefan Glienke    schedule 25.03.2014
comment
Что делает TakeInterface? Я нигде не могу найти к нему документацию. - person Johan; 21.07.2014
comment
Глупый, я подумал, что TakeInterface был синонимом _AddRef, потому что не похоже, что приведение TProc увеличит его refcount; таким образом вызывая преждевременное уничтожение I. - person Johan; 22.07.2014

Насколько мне известно, вы не можете делать то, что вам нужно, с кастингом.

Я полагаю, вы можете использовать Move для выполнения задания:

{$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.

Честно говоря, я понятия не имею, насколько это надежно. Будет ли он работать на нескольких версиях Delphi? Разумно ли полагаться на непонятные недокументированные детали реализации? Только вы можете определить, перевешивают ли ваши выгоды недостатки использования деталей реализации.

person David Heffernan    schedule 08.07.2013