Функции, возвращающие интерфейсы

Есть простой тестовый код:

program intftest;

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

type
  ITest = Interface
  ['{F76722CE-6B62-49FE-8D5E-8646558DE528}']
    function GetRefCount: Integer;
  end;

  TTest = class(TInterfacedObject, ITest);

  function GetTest: ITest;
  begin
    Result := TTest.Create;
    WriteLn(Result.GetRefCount); // 1
  end;

var
  Test: ITest;
begin
  try
    Test := GetTest;
    WriteLn(Test.GetRefCount);   // 2
    Test := nil;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

Вопрос: что я делаю не так, почему по выходу из функции GetTest() значение Test.RefCount = 2? И как в этом случае корректно уничтожить объект?..


Ответы (1 шт):

Автор решения: Андрей Ерёмин

Как правильно отметили ранее, если поместить код программы внутрь другого метода, тогда всё будет работать ожидаемо:

program intftest;

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

type
  ITest = Interface
  ['{F76722CE-6B62-49FE-8D5E-8646558DE528}']
    function GetRefCount: Integer;
  end;

  TTest = class(TInterfacedObject, ITest);

  function GetTest: ITest;
  begin
    Result := TTest.Create;
    WriteLn(Result.GetRefCount); // 1
  end;

  procedure Test;
  var
    Test: ITest;
  begin
    Test := GetTest;
    WriteLn(Test.GetRefCount); // 1
    Test := nil;
  end;    

begin
  try
    Test;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

При этом если сделать GetTest ЛОКАЛЬНОЙ ФУНКЦИЕЙ процедуры Test, то мы снова получим предыдущее поведение:

program intftest;

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

type
  ITest = Interface
  ['{F76722CE-6B62-49FE-8D5E-8646558DE528}']
    function GetRefCount: Integer;
  end;

  TTest = class(TInterfacedObject, ITest);

  procedure Test;

    function GetTest: ITest;
    begin
      Result := TTest.Create;
      WriteLn(Result.GetRefCount); // 1
    end;

  var
    Test: ITest;
  begin
    Test := GetTest;
    WriteLn(Test.GetRefCount); // 2
    Test := nil;
  end;

begin
  try
    Test;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

При этом количество ссылок доходит до нуля и объект освобождается В МОМЕНТ ЗАВЕРШЕНИЯ процедуры Test.

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

Но в консольной программе все процедуры и функции являются для основного Begin/end локальными, и поэтому Ваша переменная освободится в самом конце.

Поэтому при написании консольных приложений у Вас два выхода:

  1. В основном Begin/end вызывать только основную процедуру (например Run), для которой остальные функции не будут являться локальными (если Вы этого не захотите), а значит и не будет создаваться скрытая переменная. И на мой взгляд, это правильно.
  2. Использовать процедуры с out параметром.
→ Ссылка