【问题标题】:TGenericClass<T> containing a TObjectList<T> doesn't compile包含 TObjectList<T> 的 TGenericClass<T> 无法编译
【发布时间】:2009-06-30 10:15:30
【问题描述】:

我正在尝试编写一个泛型类,其中包含一个泛型 TObjectList,它应该只包含 TItem 的元素。

uses
  Generics.Collections;

type
  TItem = class
  end;

  TGenericClass<T: TItem> = class
  public
    SimpleList: TList<T>; // This compiles
    ObjectList: TObjectList<T>; // This doesn't compile: Compiler complaints that "T is not a class type"
  end;

这是错误的语法吗?顺便说一句:TGenericClass 编译,但列表中的项目不再是 TItem,这是我不想要的。

【问题讨论】:

    标签: delphi generics delphi-2009


    【解决方案1】:

    这是带有 D2009 编译器的known bug。它很可能很快就会得到修复,无论是在 2009 年的更新或修补程序中,还是在发布后的 Delphi 2010 (Weaver) 中。不幸的是,在那之前,您需要某种解决方法。 :(

    【讨论】:

      【解决方案2】:

      泛型类型可以有几个约束:

      • 类名,泛型类型必须是该类的子类。
      • 接口名称,泛型类型必须实现该接口。
      • 'class',泛型类型必须是类(不能与类名组合)。
      • 'record',泛型类型必须是记录。
      • 'constructor',有点模糊,但是可以创建泛型类类型的实例。

      如果你创建一个使用其他泛型的泛型,你需要复制约束,否则它将不起作用。在您的情况下, TObjectList 具有类约束。这意味着,您的 T 也需要该约束。

      很遗憾,这不能与命名类约束结合使用。

      所以我建议你使用一个接口,这些可以组合起来:

      type
        IItem = interface end;
        TItem = class (TInterfacedObject, IItem) end;
        TGenericClass<T: class, IItem> = class
        private
          FSimpleList: TList<T>;
          FObjectList: TObjectList<T>;
        end;
      

      此外,您应该将您的字段设为私有,否则任何人都可以更改它们。

      【讨论】:

        【解决方案3】:

        对于类约束的描述,我喜欢 GameCat 的回答(给了 +1)。

        我对您的代码进行了细微的修改。请注意,由于您指定 T 必须是 TItem 的后代,因此您实际上可以将 ObjectList 声明为 TObjectList&lt;TItem&gt; - 此处无需使用 T。

        或者,您可以创建各种代理。首先,请注意 GameCat 关于字段为私有的评论。

        type
          TGenericClass<T: TItem> = class
          private
            type
              D = class(TItem); // Proxy to get your T into and object list
          private
            SimpleList: TList<T>;
            ObjectList: TObjectList<D>; // Compiles now, but there is that type issue
          public
            procedure Add(Item: T); // No direct access to ObjectList
          end;
        

        Add 是如何访问对象列表的示例。事实证明,您可以毫无问题地将 Item 传递给 ObjectList.Add:

        procedure TGenericClass<T>.Add(Item: T);
        begin
          ObjectList.Add(Item);
        end;
        

        我认为这可能是一个错误,所以为了保护自己免受修复:

        procedure TGenericClass<T>.Add(Item: T);
        var
          Obj: TObject;
        begin
          Obj := Item;
          ObjectList.Add(D(Obj));
        end;
        

        但鉴于您的情况,我认为 TObjectList 应该没问题。

        【讨论】:

        • 是的,这行得通,但这正是引入通用列表以使我们摆脱的那种丑陋代码。这是一个编译器错误,纯粹而简单。它已被团队确认为一个,很快就会修复。像这样的解决方法暂时可行,但不要太执着于它。
        • 是的,同意。编译器错误很可能只有在 Stebi 修复他的代码之后才能修复 :-) 另外,我确实说过最好的方法是将 TItem 作为 ObjectList 声明的类型传递 - 这样它并不难看,运行良好并且不提供我能想到的缺点。
        • 你是对的,如果我避免向公众提供对象列表,那么一切都是可以解决的。但是我使用普通的对象列表并将项目转换为所需的类型 T 就足够了。好吧,转换不是那么容易(编译器错误也是如此?),但可以像这里一样解决:stackoverflow.com/questions/580108/…
        • 当然,如果您使用常规的 TObjectList,则需要与 TObject 进行转换。如果您使用 TObjectList,则无需进行任何强制转换。关于编译器错误 - 仅仅因为它是开放的并不意味着它会很快得到修复。 QualityCentral 充满了多年来一直存在的已打开错误。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-06
        • 1970-01-01
        • 1970-01-01
        • 2021-06-30
        相关资源
        最近更新 更多