如何继承自TObjectList<T>而不是继承自TObjectList

How to inherit from TObjectList<T> instead of inheriting from TObjectList

为什么这个程序会报内存泄漏?

{$APPTYPE CONSOLE}

uses
  System.Generics.Collections;

type
  TDerivedGenericObjectList = class(TObjectList<TObject>)
  public
    constructor Create;
  end;

constructor TDerivedGenericObjectList.Create;
begin
  inherited;
end;

var
  List: TDerivedGenericObjectList;

begin
  ReportMemoryLeaksOnShutdown := True;
  List := TDerivedGenericObjectList.Create;
  List.Add(TObject.Create);
  List.Free;
end.

您可以使用泛型。它在没有类型转换和内存泄漏的情况下工作正常(TObjectList<T>TObjectDictionary<T> 列表在自由命令时自动销毁内部对象)。

一些提示:

  • TObjectList<TPerson> -- 点赞时自动销毁人员列表 membersList.Free;

  • TList<TPerson> -- 不销毁人员列表。您必须创建析构函数并手动释放列表中的每个人;

这是您的代码示例(使用新的构造函数,没有内存泄漏并且与旧代码向后兼容 -- 参见 GetPerson):

    type
      TPerson = class
      public
        Name: string;
        Age: Integer;

        function Copy: TPerson;
      end;

      TMembers = class(TObjectList<TPerson>)
      private
        function GetPerson(i: Integer): TPerson;
      public
        property Person[i: Integer]: TPerson read GetPerson;

        constructor Create(SourceList: TMembers); overload;
      end;


    { TPerson }

    function TPerson.Copy: TPerson;
    var
      person: TPerson;
    begin
      person := TPerson.Create;
      person.Name := Self.Name;
      person.Age := Self.Age;
      Result := person;
    end;

    { TMembers }

    constructor TMembers.Create(SourceList: TMembers);
    var
      person: TPerson;
    begin
      inherited Create;

      for person in SourceList do
      begin
        Self.Add(person.Copy);
      end;
    end;

    function TMembers.GetPerson(i: Integer): TPerson;
    begin
      Result := Self[i];
    end;

    procedure TForm21.Button1Click(Sender: TObject);
    var
      person: TPerson;
      memsList1: TMembers;
      memsList2: TMembers;
    begin
      // test code

      memsList1 := TMembers.Create;

      person := TPerson.Create;
      person.Name := 'name 1';
      person.Age := 25;
      memsList1.Add(person);

      person := TPerson.Create;
      person.Name := 'name 2';
      person.Age := 27;
      memsList1.Add(person);

      memsList2 := TMembers.Create(memsList1);

      ShowMessageFmt('mems 1 count = %d; mems 2 count = %d', [memsList1.Count, memsList2.Count]);

      FreeAndNil(memsList1);
      FreeAndNil(memsList2);
    end;

您正在调用 TObjectList<T> 的无参数构造函数。这实际上是 TList<T> 的构造函数,TObjectList<T> 是从 class 派生的。

TObjectList<T>中声明的所有构造函数都接受一个名为AOwnsObjects的参数,用于初始化OwnsObjects属性。因为您正在绕过该构造函数,所以 OwnsObjects 默认为 False,并且列表的成员不会被销毁。

您应该确保调用初始化 OwnsObjectsTObjectList<T> 的构造函数。例如:

{$APPTYPE CONSOLE}

uses
  System.Generics.Collections;

type
  TDerivedGenericObjectList = class(TObjectList<TObject>)
  public
    constructor Create;
  end;

constructor TDerivedGenericObjectList.Create;
begin
  inherited Create(True);
end;

var
  List: TDerivedGenericObjectList;

begin
  ReportMemoryLeaksOnShutdown := True;
  List := TDerivedGenericObjectList.Create;
  List.Add(TObject.Create);
  List.Free;
end.

也许更好的变体是让您的构造函数也提供 AOwnsObjects 参数:

type
  TDerivedGenericObjectList = class(TObjectList<TObject>)
  public
    constructor Create(AOwnsObjects: Boolean = True);
  end;

constructor TDerivedGenericObjectList.Create(AOwnsObjects: Boolean);
begin
  inherited Create(AOwnsObjects);
end;

或者:

type
  TDerivedGenericObjectList = class(TObjectList<TObject>)
  public
    constructor Create(AOwnsObjects: Boolean = True);
  end;

constructor TDerivedGenericObjectList.Create(AOwnsObjects: Boolean);
begin
  inherited;
end;

因此,您可能想知道为什么原始版本选择了 TList<T> 构造函数而不是 TObjectList<T> 中的构造函数。好吧,让我们更详细地看一下。这是您的代码:

type
  TDerivedGenericObjectList = class(TObjectList<TObject>)
  public
    constructor Create;
  end;

constructor TDerivedGenericObjectList.Create;
begin
  inherited;
end;

当以这种方式使用 inherited 时,编译器会寻找一个与此签名完全相同的构造函数。它在 TObjectList<T> 中找不到,因为它们都有参数。它可以在 TList<T> 中找到一个,所以这就是它使用的那个。

正如您在评论中提到的,以下变体不会泄漏:

constructor TDerivedGenericObjectList.Create;
begin
  inherited Create;
end;

这种语法,与纯粹的 inherited 相比,将在替换默认参数时找到匹配的方法。所以 TObjectList<T> 的单参数构造函数被调用。

documentation有这个信息:

The reserved word inherited plays a special role in implementing polymorphic behavior. It can occur in method definitions, with or without an identifier after it.

If inherited is followed by the name of a member, it represents a normal method call or reference to a property or field, except that the search for the referenced member begins with the immediate ancestor of the enclosing method's class. For example, when:

inherited Create(...);

occurs in the definition of a method, it calls the inherited Create.

When inherited has no identifier after it, it refers to the inherited method with the same name as the enclosing method or, if the enclosing method is a message handler, to the inherited message handler for the same message. In this case, inherited takes no explicit parameters, but passes to the inherited method the same parameters with which the enclosing method was called. For example:

inherited;

occurs frequently in the implementation of constructors. It calls the inherited constructor with the same parameters that were passed to the descendant.