Присвояване на анонимен метод на интерфейсна променлива или параметър?

Анонимните методи по същество са 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 Несъвместими типове: „II интерфейс“ и „процедура, нетипизиран указател или нетипизиран параметър“

Въпрос: Какъв би бил случаят на използване за това?

Има много обекти, които не могат просто да бъдат поддържани живи с препратка към интерфейс. Следователно те са обвити в затваряне и се унищожават с него, "Интелигентни указатели":

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