Добавление одного и того же объекта дважды в TObjectDictionary освобождает объект - PullRequest
15 голосов
/ 02 августа 2011

Посмотрите на этот код:

dic:=TObjectDictionary<Integer, TObject>.Create([doOwnsValues]);
testObject:=TObject.Create;
dic.AddOrSetValue(1,testObject);
dic.AddOrSetValue(1,testObject);

Код

  1. Создает словарь, которому принадлежат содержащиеся значения
  2. Добавляет значение
  3. Добавляет то же значение снова, используя тот же ключ

Удивительно, что объект освобождается, когда вы добавляете его во второй раз.

Это предполагаемое поведение?Или ошибка в библиотеках Delphi?

В документации просто говорится: «Если объект принадлежит, когда запись удаляется из словаря, ключ и / или значение освобождаются».Таким образом, кажется немного странным освобождать объект, который я только что попросил добавить!

Есть ли способ сказать TObjectDictionary не делать этого?В настоящее время каждый раз, когда я добавляю значение, мне нужно сначала проверить, есть ли эта комбинация «ключ-значение» в Словаре.

Delphi 2010

[ПРАВКА: После прочтения всех комментариев:

Мои выводы (для чего они стоят)]

  • Это, кажется, предполагаемое поведение
  • Нет способа изменить это поведение
  • Не используйте TObjectDictionary (или любой другой подобный класс) для чего-либо, кроме общего: «Добавьте эти объекты в контейнер. Оставьте их там. Сделайте некоторые вещи. Освободите использование контейнера и всех добавленных вами объектов».Если вы делаете что-то более сложное, лучше управлять объектами самостоятельно.
  • Поведение плохо документировано, и вам следует прочитать исходный код, если вы хотите действительно знать, что происходит

[/ EDIT] * * тысяча сорок-два

Ответы [ 6 ]

9 голосов
/ 02 августа 2011

TObjectDictionary<TKey,TValue> на самом деле просто TDictionary<TKey,TValue>, в котором есть некоторый дополнительный код в методах 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;

Это, IMO, довольно простой подход, но в методе ValueNotify невозможно определить, для какого это ключа, поэтому он просто освобождает «старое» значение (нет способа проверить, это значение установлено для того же ключа).

Вы можете написать свой собственный класс (который не является тривиальным), производным от TDictionary<K,V>, или просто не использовать doOwnsValues. Вы также можете написать простую обертку, например, TValueOwningDictionary<K,V>, который использует TDictionary<K,V>, чтобы выполнить всю работу, но сам решает проблемы собственности. Я думаю, я бы сделал последнее.

7 голосов
/ 02 августа 2011

Это потому, что при повторном использовании ключа вы заменяете объект, и поскольку словарь владеет объектом, он освобождает старый.Словарь не сравнивает значение, только ключ, поэтому он не обнаруживает, что значение (объект) одинаково.Не ошибка, как задумано (ошибка пользователя IOW).

Если подумать - возможно, разработчик dict должен был позаботиться о том, чтобы и doOwnsValues, и AddOrSetValue() ... можно утверждать, что обапути ... Я предлагаю вам подать его в КК, но я бы не стал задерживать дыхание - так было сейчас, по крайней мере, в двух выпусках, поэтому вряд ли это изменится.

5 голосов
/ 02 августа 2011

Такое поведение предусмотрено дизайном, а дизайн - звуком.

Если бы класс взял на себя ответственность за то, чтобы не освобождать дубликаты, он должен был бы перебирать весь контейнер каждый раз, когда была произведена модификация, как добавление, так и удаление. Итерация проверит наличие дублирующихся значений и проверит соответственно.

Было бы катастрофически навязывать эту дьявольскую утечку производительности всем пользователям класса. Если вы хотите поместить дубликаты в список, вам нужно будет разработать политику управления сроком службы, соответствующую вашим конкретным потребностям. В этом случае нецелесообразно ожидать, что контейнер общего назначения будет поддерживать ваш конкретный шаблон использования.


В комментариях к этому ответу и многим другим было высказано предположение, что лучшим вариантом было бы проверить в AddOrSetValue, было ли заданное значение уже присвоено указанной клавише. Если это так, то AddOrSetValue может немедленно вернуться.

Я думаю, что всем ясно, что проверка на наличие дубликатов в полной общности слишком дорога, чтобы ее рассматривать. Тем не менее, я утверждаю, что есть веские конструктивные причины, по которым проверка на наличие дубликатов K и V в AddOrSetValue также будет плохой конструкцией.

Помните, что TObjectDictionary<K,V> является производным от TDictionary<K,V>. Для более общего класса сравнение равенства V является потенциально дорогостоящей операцией, поскольку у нас нет никаких ограничений относительно того, что такое V, поскольку оно является общим. Таким образом, для TDictionary<K,V> существуют причины, по которым мы не должны включать предполагаемый тест AddOrSetValue.

Можно утверждать, что мы делаем специальное исключение для TObjectDictionary<K,V>. Это, безусловно, было бы возможно. Это потребовало бы небольшой перестройки связи между двумя классами, но это вполне осуществимо. Но теперь у вас есть ситуация, когда TDictionary<K,V> и TObjectDictionary<K,V> имеют разную семантику. Это явный недостаток и должен быть сопоставлен с потенциальной выгодой от теста AddOrSetValue.

Эти универсальные классы контейнеров настолько фундаментальны, что при проектировании необходимо учитывать огромное количество вариантов использования, соображений согласованности и так далее. На мой взгляд, не разумно рассматривать TObjectDictionary<K,V>.AddOrSetValue в изоляции.

2 голосов
/ 02 августа 2011

Поскольку реализация Delphi TDictionary не позволяет использовать более одного одинакового ключа, вы можете проверить превосходную библиотеку Generic collection от Алекса Чобану. Он поставляется с TMultiMap или для вашего случая TObjectMultiMap, который позволяет использовать несколько значений для каждого ключа.

Edit: Если вы не хотите использовать несколько значений для каждого ключа, а хотите избежать добавления дубликатов в Словарь, тогда вы можете попробовать TDistinctMultiMap или TObjectDistinctMultiMap из той же библиотеки коллекций.

0 голосов
/ 28 марта 2014

Я думаю, что это ошибка.Я столкнулся с этим неделю назад.

Я использую TObjectDictionary для хранения некоторых данных телеметрии в реальном времени, которые очень часто обновляются новыми данными.

например:

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 ;

После нескольких обновлений я закончилс некоторыми неприятными нарушениями доступа и словарем, полным освобожденных объектов.

Это очень плохо спроектированный контейнер.

При обновлении необходимо проверить, совпадает ли новый объект со старымтолько и тогда он НЕ должен освобождать объект.

0 голосов
/ 02 августа 2011

Так что кажется немного странным, чтобы освободить объект, который я только что попросил добавить!

Вы не просили словарь добавить - вы называли AddorSet,и поскольку ключ уже был найден, ваш вызов был «набором», а не «добавлением».Несмотря на это, я не вижу здесь ничего странного с точки зрения поведения 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 контролирует память своих объектов, освобождая объект при переназначении его индекса»

...