2012-03-15 24 views
4

传递的方法作为一个参数一个参数是没有问题的:传递一个方法的代码作为一个类型安全的方式

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; 

但这是一个黑客和不安全 - 编译器无法检查方法的参数。

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

+0

出于兴趣,你为什么要这样做?第一个代码示例中的直接方法看起来简单得多。 – Johan 2012-03-15 14:12:16

+0

我希望我的标题编辑更清楚地表明,你没有做一些初学者会做的事情,这是一个非常深刻的黑客攻击。 – 2012-03-15 14:22:47

+1

@Johan - 摆脱很多重复的代码 – kludg 2012-03-15 14:25:50

回答

7

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

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

+1。它仍然是用虚拟方法来测试这个技巧。如果编译器在您的代码中静态地调用虚拟方法(编译器可以这样做),那么它也可能适用于虚拟方法。 – kludg 2012-03-15 17:03:52

2

免责声明:我个人不会使用此代码,并且永远不会推荐或容忍它的使用。

做这样的:

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

当然,这仍是不安全的,因为如果你把Data什么实际上与方法兼容它只会工作。


SERG问:

如何将你打电话给你的Test2,而无需创建TSomething的虚拟实例?

我想你能做到这样,静态(即非虚拟和非动态)方法:

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

当然,这一切都说明了一个怪诞的黑客,这是什么。我认为这并不比你问题中的版本更好。有没有真正干净的方式来做你所要求的。

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

+0

嗯什么是'TMethod'的定义? – Johan 2012-03-15 14:18:10

+1

@Johan它在System中声明。paseth作为TMethod =记录 代码,数据:指针; end;' – 2012-03-15 14:25:28

+2

如何在不创建TSomething的虚拟实例的情况下调用Test2? – kludg 2012-03-15 14:32:26

3

我终于采用了基于存根函数的解决方法。它没有回答我原来的问题,包含存根开销,但有重复的代码,并免费从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; 
+0

+1是更清洁,不容易出错。实际上,如果您使用最新版本,则只需传入执行该方法的匿名程序即可。 – Justmade 2012-03-16 04:02:36

2

这是使用匿名方法的例子。

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

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; 
相关问题