Delphi 将同一对象添加两次到TObjectDictionary将释放该对象
请看以下代码:Delphi 将同一对象添加两次到TObjectDictionary将释放该对象,delphi,delphi-2010,Delphi,Delphi 2010,请看以下代码: dic:=TObjectDictionary<Integer, TObject>.Create([doOwnsValues]); testObject:=TObject.Create; dic.AddOrSetValue(1,testObject); dic.AddOrSetValue(1,testObject); dic:=TObjectDictionary.Create([doownsvalue]); testObject:=TObject.Create; di
dic:=TObjectDictionary<Integer, TObject>.Create([doOwnsValues]);
testObject:=TObject.Create;
dic.AddOrSetValue(1,testObject);
dic.AddOrSetValue(1,testObject);
dic:=TObjectDictionary.Create([doownsvalue]);
testObject:=TObject.Create;
dic.AddOrSetValue(1,测试对象);
dic.AddOrSetValue(1,测试对象);
代码
- 这似乎是有意的行为
- 没有办法改变这种行为
- 不要将TObjectDictionary(或任何其他类似类)用于常见的“将这些对象添加到容器中。将它们留在那里。做一些事情。释放容器和您添加的所有对象”用法之外的任何其他用途。如果要做更复杂的事情,最好自己管理对象
- 这种行为没有很好的文档记录,如果你真的想知道发生了什么,你应该阅读源代码
[/EDIT]这是因为重复使用密钥就是在替换对象,而且由于字典拥有该对象,它会释放旧的对象。字典不比较值,只比较键,所以它不会检测到值(对象)是相同的。不是设计的bug(IOW用户错误) 再想一想——也许dict的设计者应该更加小心地使用
doownsvalue
和AddOrSetValue()。。。我们可以从两个方面进行辩论。。。我建议您将其提交给QC,但我不会屏住呼吸-至少在两个版本中都是这样,所以不太可能更改。TObjectDictionary
实际上只是一个TDictionary
,在KeyNotify
和ValueNotify
方法中有一些额外的代码:
procedure TObjectDictionary<TKey,TValue>.ValueNotify(const Value: TValue;
Action: TCollectionNotification);
begin
inherited;
if (Action = cnRemoved) and (doOwnsValues in FOwnerships) then
PObject(@Value)^.Free;
end;
过程TObjectDictionary.ValueNotify(常量值:TValue;
行动:收集通知);
开始
继承;
如果(Action=cnRemoved)和(FOwnerships中的doownsvalue),则
PObject(@Value)^.Free;
结束;
依我看,这是一种相当简单的方法,但在ValueNotify
方法中,无法判断这是哪个键,因此它只是释放“旧”值(无法检查是否为同一个键设置了此值)
您可以编写自己的类(这不是一件小事),从TDictionary
派生,或者干脆不使用doownsvalue
。您还可以编写一个简单的包装器,例如TValueOwningDictionary
,它使用TDictionary
来完成最主要的工作,但自己处理所有权问题。我想我会选择后者
所以释放一个我刚刚要求它添加的对象似乎有点奇怪
您没有要求字典添加-您称为“AddorSet”,并且由于已找到密钥,因此您的呼叫是“set”,而不是“add”。不管怎样,我认为Delphi的行为并不奇怪:在Delphi中,对象只是对象引用,简单对象没有引用计数或所有权
因为在这种情况下,字典拥有这些对象,所以它正在做它应该做的事情:“如果对象拥有,当条目从字典中删除时,键和/或值被释放”。您在重写条目[1]时删除了该值,因此“testObject”中引用的对象将立即被删除,并且您对“testObject”的引用无效
目前,每次我添加一个值时,我必须首先检查它是否已经在字典中
为什么呢?仅当使用对同一对象的引用覆盖以前使用的密钥时,才会出现您描述的行为
编辑:
也许有一些“奇怪”的地方——试试下面的测试代码:
procedure testObjectList;
var ol:TObjectList;
o,o1:TObject;
begin
ol:=TObjectList.create;
ol.OwnsObjects:=true;//default behavior, not really necessary
try
o:=TObject.create;
ol.add(o);
ol[0]:=o;
showmessage(o.ClassName);//no av-although ol[0] is overwritten with o again, o is not deleted
o1:=TObject.create;
ol[0]:=o1;
showmessage(o.ClassName);//av - when o is overwritten with o1, o is deleted
finally
ol.free
end;
end;
尽管它在(Delphi 7)帮助中说:“TObjectList控制其对象的内存,在重新分配其索引时释放对象”这种行为是设计的,设计是合理的
如果类负责不释放重复项,那么每次进行修改(包括添加和删除)时,它都必须迭代整个容器。迭代将检查任何重复的值,并进行相应的检查
将这种可怕的性能消耗强加给该类的所有用户是不明智的。如果你想在列表中添加重复项,那么你必须制定一个适合你具体需求的定制终身管理政策。在这种情况下,期望通用容器支持您的特定使用模式是不合理的
在对该答案和许多其他答案的评论中,有人建议更好的设计是在AddOrSetValue
中测试所设置的值是否已分配给指定的键。如果是这样,那么AddOrSetValue
可以立即返回
我认为,任何人都清楚,全面检查副本的成本太高,无法考虑。然而,我认为有很好的设计理由可以解释为什么在AddOrSetValue
中检查重复的K和V也是糟糕的设计
请记住,TObjectDictionary
是从TDictionary
派生而来的。对于更一般的类,比较V
的相等性可能是一个代价高昂的操作
Type TTag = class
updatetime : TDateTime;
Value : string ;
end ;
TTagDictionary:= TObjectDictionary<string,TTag>.Create([doOwnsValues]);
procedure UpdateTags(key: string; newValue: String) ;
var
tag : TTag ;
begin
if TTagDictionary.TryGetValue(key,tag) then begin // update the stored tag
tag.Value = newValue ;
tag.updatetime := now ;
TTagDictionary.AddorSetValue(key,tag) ;
else begin
tag := TTag.Create ;
tag.updatetime := now ;
tag.Vluae := newValue ;
TTagDictionary.AddorSetValue(key,tag) ;
end ;
end ;