Функция, возвращающая запись с полем интерфейса - PullRequest
8 голосов
/ 15 декабря 2011

Задав этот вопрос о полях интерфейса в записях Я предположил, что будет работать следующее (обратите внимание на утверждение):

type
  TRec <T> = record
    Intf : IInterface;
  end;

  TTestClass = class
  public
    function ReturnRec : TRec <Integer>;
  end;

  // Implementation 
  function TTestClass.ReturnRec : TRec <Integer>;
  begin
    Assert (Result.Intf = nil);    // Interface field in record should be initialized!
    Result.Intf := TInterfacedObject.Create;
  end;

Я проверил это с помощью следующего кода:

  for I := 1 to 1000 do
    Rec := Test.ReturnRec;

и утверждение не выполняется!

Где здесь моя ошибка?Какое предположение неверно?

1 Ответ

12 голосов
/ 15 декабря 2011

Функция

function ReturnRec: TRec<Integer>;

Семантически равен процедуре

procedure ReturnRec(var Result: TRec<Integer>);

[Я почти уверен, что кто-то из Embarcadero, возможно, Барри Келли или Алан Бауэр, где-то говорил это, но я не могу найти ссылку на данный момент.]

Во втором случае компилятор предполагает, что запись будет инициализирована (при необходимости) до ее передачи в ReturnRec, и не создает никакого кода инициализации для rec внутри ReturnRec. Я предполагаю, что тот же путь кода внутри компилятора взят для первого примера, и поэтому Result не инициализируется.

В любом случае, решение простое:

function TTestClass.ReturnRec : TRec <Integer>;
begin
  Result.Intf := TInterfacedObject.Create;
end;

Просто предположите, что компилятор знает, что он делает, и назначьте интерфейс, и все будет работать нормально.

EDIT

Проблема, с которой вы столкнулись, связана с циклом for. Ваш код

for I := 1 to 1000 do
  Rec := Test.ReturnRec;

компилируется во что-то вроде этого:

var
  result: TRec<Integer>;

Initialize(result);
for I := 1 to 1000 do begin
  Test.ReturnRec(result);
  rec := result;
end;

Вот почему вы повторно используете одну и ту же запись, и поэтому Result.Intf неинициализируется только в первый раз.

EDIT2

Вы можете обмануть компилятор, переместив вызов t.ReturnRec из цикла в отдельный метод.

procedure GetRec(t: TTest; var rec: TRec);
begin
  rec := t.ReturnRec;
end;

for i := 1 to 1000 do
  GetRec(t, rec);

Теперь скрытая переменная результата находится в процедуре GetRec и инициализируется при каждом вызове GetRec.

...