2012-03-15 63 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; 
相關問題