Как я могу сохранить метод интерфейса в указателе метода? - PullRequest
5 голосов
/ 05 июля 2011

Есть пример:

type
  TDelegate = procedure of object;

  I1 = interface
  ['{31D4A1C7-668B-4969-B043-0EC93B673569}']
    procedure P1;
  end;

  TC1 = class(TInterfacedObject, I1)
    procedure P1;
  end;

...

var
  obj: TC1;
  int: I1;
  d: TDelegate;
begin
  obj := TC1.Create;
  ...
  int := obj; // "int" may contains TSomeAnotherObjectWhichImplementsI1
  d := obj.P1; // <- that's fine
  d := int.P1; // <- compiler error
end;

Так как я могу сделать последнюю операцию? Я не знаю, какой тип объекта будет присутствовать в переменной "int", поэтому я не могу использовать Typecast. Но я все равно знаю, что он будет присутствовать (потому что если вы реализуете интерфейс, вы должны реализовать все его методы). Так почему я не могу просто получить указатель на этот метод? Может есть другой способ? Спасибо.

Ответы [ 3 ]

4 голосов
/ 05 июля 2011

Может быть, есть другой способ?

Лучшим способом было бы изменить код, который ожидает, что TDelegate также принимает i1.Если вы написали код, изменение будет тривиальным, и в основном это лучшее, что вы можете сделать.Если вы не можете изменить код, ожидающий TDelegate, и вам абсолютно необходимо вызвать процедуру из интерфейса, вам может потребоваться создать объект адаптера, что-то вроде этого:

TDelegateAdapter = class
private
  Fi1: i1;
public
  constructor Create(Ani1: i1);

  procedure P;
end;

constructor TDelegateAdapter.Create(Ani1: i1);
begin
  Fi1 := Ani1;
end;

procedure TDelegateAdapter.P;
begin
  Fi1.P1;
end;

Затем вкод, где вам нужно назначить TDelegate, сделайте что-то вроде этого:

var Adapter: TDelegateAdapter;
    Intf: i1; // assumed assigned
    ObjectExpectingDelegate: TXObject; // assumed assigned
begin
  Adapter := TDelegateAdapter.Create(Intf);
  try
    ObjectExpectingDelegate.OnSomething := Adapter.P;
    try
      ObjectExpectingDelegate.PerformWork;
    finally ObjectExpectingDelegate.OnSomething := nil;
    end;
  finally Adapter.Free;
  end;
end;

Edit

Если вы используете версию Delphi, которая поддерживает анонимные методы, вы можете реализовать адаптер Delegate, используя такиеанонимные методы, требующие только одного «адаптера» на сигнатуру процедуры.Delphi реализует анонимные методы за кулисами, используя интерфейсы, поэтому производительность во время выполнения будет хорошей, не нужно беспокоиться.

Приведенный ниже код является демонстрационной консольной реализацией анонимного адаптера делегата.Посмотрите прямо на последний блок begin - end, чтобы увидеть магию.

program Project29;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type

  // This is the type of the anonymous method I want to use
  TNoParamsProc = reference to procedure;

  // This implements the "delegate" adapter using an anonymous method
  TAnonymousDelegateAdapter = class
  private
    NoParamsProc: TNoParamsProc;
  public
    constructor Create(aNoParamsProc: TNoParamsProc);

    procedure AdaptedDelegate;
  end;

  { TAnonymousDelegateAdapter }

  procedure TAnonymousDelegateAdapter.AdaptedDelegate;
  begin
    NoParamsProc;
  end;

  constructor TAnonymousDelegateAdapter.Create(aNoParamsProc: TNoParamsProc);
  begin
    NoParamsProc := aNoParamsProc;
  end;

  // --------- test code follows ----------

type

  // Interface defining a single method.
  ISomething = interface
    procedure Test;
  end;

  // Implementation of the interface above
  TSomethingImp = class(TInterfacedObject, ISomething)
  public
    procedure Test;
  end;

  // Definition of delegate
  TNoParamsDelegate = procedure of object;

  { TSomethingImp }

  procedure TSomethingImp.Test;
  begin
    WriteLn('Test');
  end;

// ---- Test program to see it all in action. ---

var intf: ISomething;
    Dlg: TNoParamsDelegate;

begin
  intf := TSomethingImp.Create;
  // Here I'll create the anonymous delegate adapter, notice the "begin - end"
  // in the constructor call; That's the anonymous method. Runtime performance
  // of anonymous methods is very good, so you can use this with no warries.
  // My anonymous method uses the "intf" variable and calls the method "Test"
  // on it. Because of that the "intf" variable is "captured", so it doesn't run
  // out of scope as long as the anonymous method itself doesn't run out of scope.
  // In other words, you don't risk having your interface freed because it's reference
  // count reaches zero. If you want to use an other interface, replace the code
  // in the begin-end.
  with TAnonymousDelegateAdapter.Create(procedure begin intf.Test; end) do
  try
    Dlg := AdaptedDelegate;
    Dlg;
  finally Free;
  end;

  Readln;
end.
2 голосов
/ 05 июля 2011

Я бы предположил, что по крайней мере одна из причин, по которой компилятор блокирует это, заключается в том, что procedure of object не является управляемым типом, и поэтому вы будете обходить подсчет ссылок интерфейса.

Другая причина, по которой это будет запрещено, заключается в том, что механизм вызова для метода интерфейса отличается от механизма вызова для procedure of object.

0 голосов
/ 06 июля 2011

Я предложу вам, вероятно, маловероятный вариант, но он переключается на freepascal / lazarus

Я проверил, ваши фрагменты кода компилируются и работают там.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...