【问题标题】:Do Delphi generic collections override equals?Delphi 泛型集合是否覆盖等于?
【发布时间】:2010-07-30 01:49:02
【问题描述】:

这是一般收藏大师的问题。

我很震惊地发现 TList 没有覆盖 equals。看看这个例子:

list1:=TList<String>.Create;       
list2:=TList<String>.Create;

list1.Add('Test');
list2.Add('Test');

Result:=list1.Equals(list2);

“结果”为假,即使两个列表包含相同的数据。它使用默认的 equals() (它只是比较两个引用是否相等)。

查看代码,似乎所有其他泛型集合类型也是如此。

这是对的,还是我错过了什么??

如果在实践中尝试使用 TList,这似乎是一个大问题。我该如何解决这个问题?我是否创建自己的 TBetterList 来扩展 TList 并覆盖 equals 以做一些有用的事情? 或者我会遇到 Delphi 泛型的进一步复杂性......?

[编辑:到目前为止,我有一个答案,有很多赞成票,但它并没有真正告诉我我想知道什么。我会尝试重新表述这个问题]

在 Java 中,我可以这样做:

List<Person> list1=new ArrayList<Person>();
List<Person> list2=new ArrayList<Person>();
list1.add(person1);
list2.add(person1);
boolean result=list1.equals(list2);

result 将为 true。我不需要继承任何东西,它就可以工作。

如何在 Delphi 中做同样的事情?

如果我在 Delphi 中编写相同的代码,result 将结束 false。

如果有一个解决方案只适用于 TObjects 而不是 Strings 或 Integers 那么这也将非常有用。

【问题讨论】:

    标签: delphi


    【解决方案1】:

    泛型与这个问题的症结没有直接关系:选择什么构成 Equals() 测试的有效基本实现是完全任意的。 TList.Equals() 的当前实现至少与(我认为)VCL 中所有其他类似的基类一致,并且我所说的类似不仅仅是指集合或泛型类。 p>

    例如,TPersistent.Equals() 也进行简单的参考比较 - 它不比较任何已发布属性的值,这可以说是您拥有的相等测试类型的语义等价物记住 TList

    您谈到扩展 TBetterList 并在派生类中做一些有用的事情,就好像这是一种繁重的义务,但这正是面向对象软件开发的本质。

    核心框架中的基类提供了通用实用程序定义的东西。您认为是 Equals() 的有效实现可能与其他人的需求有很大不同(或者实际上在您自己的项目中,从该基类派生的一个类到另一个类)。

    所以是的,然后由您来实现对提供的基类的扩展,该扩展将反过来提供一个对您特别有用的基类。

    但这不是问题。

    这是一个机会。

    :)

    但是,您肯定会在泛型方面遇到更多问题,而不仅仅是在 Delphi 中。 ;)

    【讨论】:

    • 感谢您提供如此精美的答案。我觉得你的观点有点令人困惑。几点: 1) TStringList 有一个 equals 方法,其行为与我描述的完全一样,因此在 VCL 中有一个先例,但也许你在这里有一点 2) 默认的 equals() 应该满足最常见的要求行为.对于自定义行为,您可以使用 TComparer (这是我在这种情况下最终所做的,因为默认实现是无用的) 3)比较两个列表是否相等几乎不是特定要求。我希望有一个像样的馆藏图书馆来提供它。
    • TList 与 TStringList 不同(并且在语义上与 TList 不同)。一个是可以是任何类型的列表的类——字符串只是其中之一。 TStringList 只是一个字符串列表。如果一个 TStringList 以相同的顺序包含相同的字符串,则可以合理地说它们与另一个 TStringList 相同。但请注意,TStrings.Equals() 不考虑任何关联的 Object[] 数据,这在特定情况下可能很重要(并且可能被 some 视为当前实现中的缺陷/问题/错误人)……(续……)
    • ...(续)。您建议 TList.Equals 应该对其包含的每个 元素进行简单比较...适用于字符串和整数,但是其他 TObjects 派生类型呢?记录类型呢?接口?在这些情况下,所需的比较更有问题,通用解决方案是不可能的。在这种情况下,泛型可能被认为是问题的原因,但这仅仅是因为它们比非泛型基类中的常见情况更
    • 这不正是 Java 的 ArrayList 所做的吗? download-llnw.oracle.com/javase/1.5.0/docs/api/java/util/… 至少它对对象执行此操作(您不能将原语存储在列表中)。 “记录类型呢?接口?”如果我们将 Type T 限制为 TObject 的后代怎么办?然后我们可以在 TList 中的每个关联的 TObject 对上调用 TObject.equals。 (只说“列表中的对象”是不是不合适?它更容易从舌头上滚下来)免责声明:我只知道足够多的泛型和足够多的 Delphi 是危险的
    • Awmross,原来的问题是 TList.Equals 不会对列表的内容进行深度比较。 Deltics 正确地指出它不能的原因是没有办法编写代码来执行 TList 可能持有的所有类型的通用比较。您建议的解决方案是限制它可以容纳的类型,但这会破坏 TList! (请注意,这甚至不能解决您最初的问题,因为 string 不是从 TObject 下降的。)
    【解决方案2】:

    归结为:

    在 Java(和 .NET 语言)中,所有类型都源自 Object。在 Delphi 中,整数、字符串等不源自 TObject。它们是原生类型,没有类定义。

    这种差异的影响有时是微妙的。在泛型集合的情况下,Java 可以假设任何类型都有Equals 方法。因此,编写Equals 的默认实现很简单,只需遍历两个列表并在每个对象上调用Equals 方法即可。

    来自 Java 6 Open JDK 中的AbstractList 定义:

    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof List))
            return false;
    
        ListIterator<E> e1 = listIterator();
        ListIterator e2 = ((List) o).listIterator();
        while(e1.hasNext() && e2.hasNext()) {
            E o1 = e1.next();
            Object o2 = e2.next();
            if (!(o1==null ? o2==null : o1.equals(o2)))
                return false;
        }
        return !(e1.hasNext() || e2.hasNext());
    }
    

    正如您所见,默认实现毕竟并没有那么深入的比较。您仍然会覆盖 Equals 以比较更复杂的对象。

    在 Delphi 中,因为不能保证 T 的类型是一个对象,所以 Equals 的默认实现是行不通的。因此,Delphi 的开发人员别无选择,只能将 TObject.Equals 覆盖给应用程序开发人员。

    【讨论】:

    • 因此,TList 既可以存储基元也可以存储对象这一事实(与 Java 版本不同,它仅限于对象)使得比较 TList 的成员更加困难。 DeHL 库实现了一个适用于所有泛型类型的 equals()(请参阅我接受的答案)。我不知道它是如何工作的,但我猜它使用 RTTI(即反射)来发现类型并使用适当的比较。毕竟,如果需要,您始终可以使用反射绕过类型系统。所以我不确定这是不可能的;我猜 Delphi 开发人员只是决定不这样做。
    • @awmross 这可能是他们将来尝试的方法。 RTTI 在 Delphi 2010 中得到了极大的改进,但在 Delphi 2009 中引入泛型时有点麻烦。
    • 进一步阅读,我发现 2009 年的 RTTI 仅适用于已发布的项目。那肯定很麻烦!并且有限。通用集合是在 2009 年推出的? (我从 2006 年直接跳过到 2010 年)。因此,正如您所说,通用 equals 是不可能的。我想知道他们现在是否可以更改 TList 中 equals 的实现,或者这样做是否为时已晚;出于向后兼容性的原因。
    • 您可以在泛型类中使用 TypeInfo(T) 运算符来获取“尚未定义的类型”的类型信息。然后,您可以决定使用什么比较器等。它在某些时候会变得复杂。 DeHL 在 DeHL.Types 中定义了这些东西。你可以看看它是如何工作的。
    【解决方案3】:

    我环顾四周,在 DeHL(一个开源 Delphi 库)中找到了一个解决方案。 DeHL 有一个 Collections 库,有自己的替代 List 实现。在向开发者询问此事后,比较通用 TLists 的功能被添加到当前不稳定的 DeHL 版本中。

    所以这段代码现在会给我我正在寻找的结果(在 Delphi 中):

    list1:=TList<Person>.Create([Person.Create('Test')]);
    list2:=TList<Person>.Create([Person.Create('Test')]);
    
    PersonsEqual:=list1.Equals(list2); // equals true
    

    它适用于所有类型,包括字符串和整数类型

    stringList1:=TList<string>.Create(['Test']);
    stringList2:=TList<string>.Create(['Test']);
    
    StringsEqual:=stringList1.Equals(stringList2); // also equals true
    

    甜!

    您需要查看最新的不稳定版本的 DeHL (r497) 才能使其正常工作。当前稳定版本 (0.8.4) 与标准 Delphi TList 具有相同的行为。

    请注意,这是最近的更改,可能不会进入 DeHL 的最终 API(我当然希望如此)。

    所以也许我会使用 DeHL 而不是标准的 Delphi 集合?真可惜,因为我更喜欢尽可能使用标准平台库。我将进一步研究 DeHL。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-03-08
      • 2022-01-21
      • 2023-03-31
      • 1970-01-01
      • 2012-09-09
      • 1970-01-01
      相关资源
      最近更新 更多