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,测试对象);
代码

  • 创建拥有包含的值的字典
  • 增加一个值
  • 使用相同的键再次添加相同的值
  • 令人惊讶的是,当您第二次添加对象时,该对象被释放

    这是故意的行为吗?还是Delphi库中的一个bug

    文档只是说“如果对象是所有者,当条目从字典中删除时,键和/或值将被释放”。所以释放一个我刚刚要求它添加的对象似乎有点奇怪

    有没有办法告诉TObjectDictionary不要这样做?目前,每次添加值时,我都必须首先检查该键值组合是否已在字典中

    德尔福2010

    [编辑: 阅读所有评论后:

    我的结论(就其价值而言)]

    • 这似乎是有意的行为
    • 没有办法改变这种行为
    • 不要将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 ;