为什么 Delphi 的 TStringList.InsertObject() 方法在列表已排序时会抛出异常?

6
在 Delphi 6 中,如果您尝试将一个对象插入到已排序的 TStringList(Sorted = true)中,则会抛出一个异常,警告您不允许在已排序的列表上使用 InsertObject()。如果调用 InsertObject() 必然意味着破坏了列表的排序顺序,那么我可以理解这一点。但是,考虑到 TStringList.Find() 方法:
function TStringList.Find(const S: string; var Index: Integer): Boolean;

如果将给定的字符串添加到列表中,返回一个索引,告诉您插入索引应该是什么。在使用该索引调用InsertObject()后,排序列表应该仍然保持排序状态。我已经检查了TStringList的Delphi源代码,它似乎证实了我的说法。

目前,我只是创建了一个新的TStringList子类,覆盖了InsertObject()方法,并且如果在排序列表上调用InsertObject()方法,则不会抛出异常,但我希望确保没有隐藏的危险我没有看到。

-- roschler


2
为什么不直接调用 AddObject,它会神奇地添加到正确的索引位置? - David Heffernan
5个回答

7
你应该在排序列表上只调用AddObject方法。
如果InsertObject在已排序列表上检查“正确”的索引,那么你会遇到测试噩梦:在某些情况下,你的代码看起来可以工作,但如果输入数据发生变化,它突然会开始抛出异常。而如果InsertObject忽略Index参数,则其行为将极不直观。
如果列表已排序,最好让InsertObject总是抛出错误。

如果您还有其他原因想要索引:AddObject将返回索引。而要根据自己找到的索引决定是否添加对象,请参见Sertac Akyuz的代码。 - NGLN

3
错误信息对我来说非常清晰:在排序的TStringlist上调用Insert或InsertObject是不允许的。当sorted为true时,stringlist会自动处理新条目的位置以保持列表排序。假设允许使用Insert,那么stringlist如何知道给定的索引不会破坏排序呢?它必须找到正确的索引,将其与给定的索引进行比较,然后呢?要么使用找到的索引,要么抛出异常。因此,只允许使用Add或AddObject。

2
为了避免重复进行二进制搜索,您可以使用受保护的InsertItem方法:
type
  THackSL = class(TStringList);

...

var
  i: Integer;
  s: string;
begin
  ...
  if not MyStringList.Find(s, i) then
    THackSL(MyStringList).InsertItem(i, s, nil);
 

@Robert - 不用谢!但是我本意是把这个作为答案发布,而不是提示。我误解了你的问题吗?你是否在避免使用子类化字符串列表来搜索重复项? - Sertac Akyuz
好的,这并不是一个性能提示:提问者已经使用了 Find。;) - NGLN
@NGLN - 嗯,如果他在“查找”之后调用“添加”或“添加对象”,那么他将使用它两次.. :) - Sertac Akyuz
这有点是我的观点。如果你想要先自行确定索引(使用Find),那么 Sertac 的这段代码“应该”被用来避免重复搜索。但在这种情况下,就像Sertac所说的那样,没有性能提升。我是指与使用AddObject相比较。在插入之前进行搜索的唯一原因可能是获取索引以决定是否要插入。 - NGLN
@NGLN - 好的,同意,我在比较 Find + Add/AddObjectFind + InsertItem - Sertac Akyuz

1

没有 Delphi6 进行检查,但在 Delphi XE 中是相同的。如果列表已排序,则应使用 AddObject。当列表为您排序项目时,在特定位置插入对象并没有真正意义。


0
请使用TStringList.Add代替。它会自动检查重复项并将字符串插入正确的位置。

根据Duplicates属性进行操作 http://docwiki.embarcadero.com/VCL/en/Classes.TStringList.Duplicates - Gerry Coll

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接