将匿名方法分配给接口变量或参数?

15

匿名方法本质上是带有Invoke方法的interface

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”和“procedure, untyped pointer or untyped parameter”

问题:这个有什么用途?

有很多对象无法仅通过接口引用来保持其存在。因此,它们被封装在闭包中,并随之销毁,“智能指针”(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

有时不仅需要将单个对象保持活动状态,还需要与其一起保留上下文。想象一下您拥有一个 TDictionary<TKey, TValue> 并从中提取了一个枚举器:TEnumerator<TKey>TEnumerator<TValue>TEnumerator<TPair<TKey, TValue>>。或者字典包含并“拥有”TObject。然后,为了创建一个独立的引用,新对象和字典的闭包都会进入一个新的闭包中:

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 类型的值分配给接口类型,这意味着没有统一、安全和未来可靠的方式来引用所有闭包类型以保持它们的生命周期。


2
这个有什么使用场景呢?其次,匿名方法并不是必须的接口。它们被实现为接口,但那只是纯接口细节。 - David Heffernan
感谢您添加一些解释和评论。我必须说,我仍然不太明白您的意图。我无法理解通过获取闭包的IInterface引用而不是仅持有方法引用的好处。但是,了解到您可以编写class(TInterfacedObject, TProc)确实让我感到很有趣! - David Heffernan
3个回答

12

这非常简单。我将向您展示两种方法。

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;

TakeInterface 是什么作用?我无法在任何地方找到它的文档。 - Johan
1
我真傻,我以为 TakeInterface 是 _AddRef 的同义词,因为它看起来不像是将 TProc 强制转换会增加其引用计数;从而导致 I 过早地被销毁。 - Johan

6
据我所知,您不能使用强制转换来完成您所需的操作。
您可以尝试使用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上运行吗?依赖于晦涩难懂的未记录实现细节是明智的吗?只有您才能确定所获得的收益是否超过依赖实现细节所带来的负面影响。


0

最简单的转换方式如下:

IProc((@P)^)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接