如何比较TFunc / TProc包含对象的函数/过程? [英] How to compare TFunc/TProc containing function/procedure of object?

查看:149
本文介绍了如何比较TFunc / TProc包含对象的函数/过程?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们使用 TList< TFunc< Boolean>> 与对象它现在想再次 Remove()一些条目。但它不工作,因为显然你只是不能比较这些引用... 可靠。

We use a TList<TFunc<Boolean>> with some function ... of objects in it and now want to Remove() some of the entries again. But it doesn't work because obviously you simply can not compare these reference to ... thingies reliably.

这里有一些测试代码:

program Project1;

{$APPTYPE CONSOLE}

uses
  Generics.Defaults,
  SysUtils;

type
  TFoo = class
  strict private
    FValue: Boolean;
  public
    constructor Create();
    function Bar(): Boolean;
  end;

{ TFoo }

function TFoo.Bar: Boolean;
begin
  Result := FValue;
end;

constructor TFoo.Create;
begin
  inherited;

  FValue := Boolean(Random(1));
end;

function IsEqual(i1, i2: TFunc<Boolean>): Boolean;
begin
  Result := TEqualityComparer<TFunc<Boolean>>.Default().Equals(i1, i2);
end;

var
  s: string;
  foo: TFoo;
  Fkt1, Fkt2: TFunc<Boolean>;

begin
  try
    Foo := TFoo.Create();

    WriteLn(IsEqual(Foo.Bar, Foo.Bar));             // FALSE (1)
    WriteLn(IsEqual(Foo.Bar, TFoo.Create().Bar));   // FALSE (2)

    Fkt1 := function(): Boolean begin Result := False; end;
    Fkt2 := Fkt1;
    WriteLn(IsEqual(Fkt1, Fkt2));                   // TRUE  (3)

    Fkt2 := function(): Boolean begin Result := False; end;
    WriteLn(IsEqual(Fkt1, Fkt2));                   // FALSE (4)

    Fkt2 := function(): Boolean begin Result := True; end;
    WriteLn(IsEqual(Fkt1, Fkt2));                   // FALSE (5)

    FreeAndNil(Foo);
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
  Readln(s);
end.

我们尝试了一切 ,=运算符,比较指针等。

We tried virtually everything, = operator, comparing pointers, etc..

我们甚至尝试了一些非常讨厌的事情,比如重复地转换到 PPointer 和解引用,直到我们得到相等的值,当然没有产生令人满意的结果。)

We even tried some really nasty things like repeatedly casting to PPointer and dereferencing until we get equal values, but that of course didn't yield satisfying results either =).


  • 情况(2),(4)和


  • 情况(3)很简单,也很好。

  • ,这是我们无法工作的。

  • Case (2), (4) and (5) are OK, as there are in fact distinct functions.
  • Case (3) is trivial and OK, too.
  • Case (1) is what we want to detect, and this is what we can't get to work.

恐怕,Delphi隐秘地创建两个不同的匿名函数,到 Foo.Bar 。在这种情况下,我们将是完全无能为力的,除非我们想要通过一个未知内存的陨石...我们不会。

I fear, Delphi stealthily creates two distinct anonymous functions that forward the call to Foo.Bar. In this case we'd be completely powerless, unless we wanted to wade through a morass of unknown memory... and well, we don't.

推荐答案

您必须通过其他方式将名称或索引与其关联。匿名方法没有名称,并且可能捕获状态(因此它们被重新创建每个实例);有没有微不足道的方式,使它们可比较而不破坏封装。

You'll have to associated a name or index with them by some other means. Anonymous methods don't have names and may capture state (so they are recreated per instance); there is no trivial way to make them comparable without breaking encapsulation.

你可以得到的方法引用后面的对象,如果它后面确实有一个对象保证这一点 - 方法引用的接口是用COM语义实现的,他们真正需要的是一个COM vtable):

You can get at the object behind the method reference, if there is indeed an object behind it (there's no guarantee of this - the interfaces that method references are implemented in terms of COM semantics, all they really need is a COM vtable):

function Intf2Obj(x: IInterface): TObject;
type
  TStub = array[0..3] of Byte;
const
  // ADD [ESP+$04], imm8; [ESP+$04] in stdcall is Self argument, after return address
  add_esp_04_imm8: TStub = ($83, $44, $24, $04);
  // ADD [ESP+$04], imm32
  add_esp_04_imm32: TStub = ($81, $44, $24, $04);

  function Match(L, R: PByte): Boolean;
  var
    i: Integer;
  begin
    for i := 0 to SizeOf(TStub) - 1 do
      if L[i] <> R[i] then
        Exit(False);
    Result := True;
  end;

var
  p: PByte;
begin
  p := PPointer(x)^; // get to vtable
  p := PPointer(p)^; // load QueryInterface stub address from vtable

  if Match(p, @add_esp_04_imm8) then 
  begin
    Inc(p, SizeOf(TStub));
    Result := TObject(PByte(Pointer(x)) + PShortint(p)^);
  end
  else if Match(p, @add_esp_04_imm32) then
  begin
    Inc(p, SizeOf(TStub));
    Result := TObject(PByte(Pointer(x)) + PLongint(p)^);
  end
  else
    raise Exception.Create('Not a Delphi interface implementation?');
end;

type
  TAction = reference to procedure;

procedure Go;
var
  a: TAction;
  i: IInterface;
  o: TObject;
begin
  a := procedure
    begin
      Writeln('Hey.');
    end;
  i := PUnknown(@a)^;
  o := i as TObject; // Requires Delphi 2010
  o := Intf2Obj(i); // Workaround for non-D2010
  Writeln(o.ClassName);
end;

begin
  Go;
end.

这将(当前)打印 Go $ 0 $ ActRec ;但是如果你有一个第二个匿名方法,在结构上是相同的,它将导致第二个方法,因为匿名方法体不会被比较结构相等(这将是一个高成本,低价值的优化,因为程序员不太可能做这样的事情,大型结构比较不便宜)。

This will (currently) print Go$0$ActRec; but if you have a second anonymous method, structurally identical, it will result in a second method, because anonymous method bodies are not compared for structural equality (it would be a high-cost, low-value optimization, as it's unlikely the programmer would do such a thing, and large structural comparisons aren't cheap).

如果你使用的是更新版本的Delphi,你可以使用RTTI类的这个对象并尝试和比较字段,并自己实现结构比较。

If you were using a later version of Delp you could use RTTI on the class of this object and try and compare fields, and implement structural comparison yourself.

这篇关于如何比较TFunc / TProc包含对象的函数/过程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆