【问题标题】:Passing a method's code as an argument in a typesafe way以类型安全的方式将方法的代码作为参数传递
【发布时间】:2012-04-01 01:25:27
【问题描述】:

将方法作为参数传递不是问题:

type
  TSomething = class
    Msg: string;
    procedure Show;
  end;

procedure TSomething.Show;
begin
  ShowMessage(Msg);
end;

type TProc = procedure of object;

procedure Test(Proc: TProc);
begin
  Proc;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Smth: TSomething;

begin
  Smth:= TSomething.Create;
  Smth.Msg:= 'Hello';
  Test(Smth.Show);
end;

我需要一些棘手的东西 - 只传递方法的代码部分。我知道我能做到:

procedure Test2(Code: Pointer);
var
  Smth: TSomething;
  Meth: TMethod;

begin
  Smth:= TSomething.Create;
  Smth.Msg:= 'Hello Hack';
  Meth.Data:= Smth;
  Meth.Code:= Code;
  TProc(Meth);
  Smth.Free;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Test2(@TSomething.Show);
end;

但这是一种 hack 且不安全 - 编译器无法检查方法的参数。

问题:是否可以以类型安全的方式做同样的事情?

【问题讨论】:

  • 只是出于兴趣,为什么要这样做?第一个代码示例中的直接方法看起来要简单得多。
  • 我希望我的标题编辑能更清楚地表明你没有做初学者会做的事情,这是一个非常深刻的 hack。
  • @Johan - 摆脱大量重复代码

标签: delphi methods arguments type-safety


【解决方案1】:

这是一个使用匿名方法的示例。

没有代码重复和类型安全的方法调用。

type
  TSmth = class
    procedure Method1;
    procedure Method2;
  end;

procedure Test;
type
  TMyMethodRef = reference to procedure;
  PMyTestRef = reference to procedure(aMethod :TMyMethodRef);
var
  TestP : PMyTestRef;
  Smth : TSmth;
begin
  TestP :=
    procedure( aMethod : TMyMethodRef)
    begin
      Smth := TSmth.Create;
      try
        // setup Smth
        aMethod;
        // test Smth 
      finally
        Smth.Free;
      end;
    end;

  TestP(Smth.Method1); // Test Method1
  TestP(Smth.Method2); // Test Method2    
end;

【讨论】:

    【解决方案2】:

    我终于采用了基于存根函数的解决方法。它没有回答我最初的问题,包含一个存根开销,但解决了我的重复代码问题,并且没有 hackish 代码:

    type
      TSmth = class
        procedure Method1;
        procedure Method2;
      end;
    
    type
      TDoMethod = procedure(Instance: TSmth);
    
    procedure DoMethod1(Instance: TSmth);
    begin
      Instance.Method1;
    end;
    
    procedure DoMethod2(Instance: TSmth);
    begin
      Instance.Method2;
    end;
    
    procedure TestMethod(DoMethod: TDoMethod);
    var
      Smth: TSmth;
    
    begin
      Smth:= TSmth.Create;
    { a lot of common setup code here }
      DoMethod(Smth);
    { a lot of common check code here }
      Smth.Free;
    end;
    
    procedure TestMethod1;
    begin
      TestMethod(DoMethod1);
    end;
    
    procedure TestMethod2;
    begin
      TestMethod(DoMethod2);
    end;
    

    【讨论】:

    • +1 是的,更干净,不容易出错。实际上,如果您使用最新版本,您可以传入执行该方法的匿名过程。
    【解决方案3】:

    我终于明白了。具有类型检查,无需为调用事件声明变量!

    type
    
      TSomething = class
        Msg: string;
        procedure Show;
        procedure ShowWithHeader(Header : String);
      end;
    
      TProc = procedure of object;
      TStringMethod = procedure(S : String) of Object;
    
    procedure TSomething.Show;
    begin
      ShowMessage(Msg);
    end;
    
    procedure TSomething.ShowWithHeader(Header: String);
    begin
      ShowMessage(Header + ' : ' + Msg);
    end;
    
    procedure Test2(Code: TProc);
    var
      Smth: TSomething;
    begin
      Smth:= TSomething.Create;
      Smth.Msg:= 'Hello Hack 2';
      TMethod(Code).Data := Smth;
      Code;
      Smth.Free;
    end;
    
    procedure Test3(Code: TStringMethod; S : String);
    var
      Smth: TSomething;
    begin
      Smth:= TSomething.Create;
      Smth.Msg:= 'Hello Hack 3';
      TMethod(Code).Data := Smth;
      Code(S);
      Smth.Free;
    end;
    
    procedure TForm4.btn1Click(Sender: TObject);
    begin
      Test2(TSomething(nil).Show);
    //  Test2(TSomething(nil).ShowWithHeader); // Cannot Compile
    end;
    
    procedure TForm4.btn2Click(Sender: TObject);
    begin
    //  Test3(TSomething(nil).Show,'Hack Header');  // Cannot Compile
      Test3(TSomething(nil).ShowWithHeader,'Hack Header');
    end;
    

    【讨论】:

    • +1。剩下的就是用虚拟方法来测试这个技巧;如果编译器在您的代码中静态调用虚拟方法(编译器可以做到),它可能也适用于虚拟方法。
    【解决方案4】:

    免责声明:我个人绝不会使用此代码,也绝不会推荐或纵容其使用。

    这样做:

    procedure Test2(Method: TProc);
    var
      Smth: TSomething;
    begin
      Smth:= TSomething.Create;
      Smth.Msg:= 'Hello Hack';
      TMethod(Method).Data:= Smth;
      Method();
    end;
    

    当然,这仍然是不安全的,因为只有当您放入 Data 的内容实际上与该方法兼容时,它才会起作用。


    军士问:

    如何在不创建 TSomething 虚拟实例的情况下调用 Test2?

    我想你可以这样做,对于静态(即非虚拟和非动态)方法:

    var
      Obj: TSomething;
    ....
    Test2(Obj.Show);//no need to actually create Obj
    

    当然,所有这些都说明了这是多么荒谬的黑客行为。我认为这并不比您问题中的版本好。没有真正干净的方式来做你所要求的。

    我怀疑解决您的实际问题的正确方法是使用 RTTI 调用该方法。

    【讨论】:

    • TMethod的具体定义是什么?
    • @Johan 在 System.pas 中声明为TMethod = record Code, Data: Pointer; end;
    • 如何在不创建 TSomething 虚拟实例的情况下调用 Test2
    • 这不是泄密吗,你打电话给create 但是你在哪里销毁?
    • @Johan 当然是泄漏。我刚刚从问题中复制了代码。
    猜你喜欢
    • 1970-01-01
    • 2015-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 2022-01-22
    • 1970-01-01
    相关资源
    最近更新 更多