Передача кода метода в качестве аргумента безопасным способом - PullRequest
5 голосов
/ 15 марта 2012

Передача метода в качестве аргумента не является проблемой:

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;

но это хак и небезопасно - компилятор не может проверить аргументы метода.

Вопрос: можно ли сделать то же самое типичным способом?

Ответы [ 4 ]

7 голосов
/ 15 марта 2012

Я наконец понял.С проверкой типа и без необходимости объявлять переменную для вызывающего события!

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;
3 голосов
/ 16 марта 2012

Я, наконец, принял обходной путь, основанный на функциях заглушки.Он не отвечает на мой первоначальный вопрос, содержит заглушки, но решает мою проблему с дублированным кодом и свободен от хакерского кода:

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;
2 голосов
/ 07 февраля 2013

Это пример использования анонимных методов.

Нет дублирования кода и безопасных вызовов методов.

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 голосов
/ 15 марта 2012

Отказ от ответственности : лично я никогда бы не использовал этот код и никогда не мог рекомендовать или оправдывать его использование.

Сделай так:

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

Конечно, это все еще небезопасно, так как будет работать, только если то, что вы положили в Data, действительно совместимо с методом.


Сергей спрашивает:

Как вы будете называть свой Test2 без создания фиктивного экземпляра TSomething?

Полагаю, вы можете сделать это следующим образом для статических (то есть не виртуальных и не динамических) методов:

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

Конечно, все это иллюстрирует, какой это гротескный хак. Я думаю, что это не лучше, чем версия в вашем вопросе. Там нет реального чистого способа сделать то, что вы просите.

Я подозреваю, что правильным способом решения вашей реальной проблемы было бы использование RTTI для вызова метода.

...