Не удается удалить элемент из Iesi.Collections.Generic ISet<T>
У меня есть коллекция типа:Iesi.Collections.Generic
public ISet<ItemBinding> ItemBindings { get; set; }
где ItemBinding
является Domain.Model
Я инициализирую коллекцию следующим образом:
ItemBindings = new HashedSet<ItemBinding>();
и я заполняю коллекцию членами.
Когда я хочу удалить элемент из этой коллекции, я не могу удалить его.
private void OnRemove(ItemBinding itemToRemove) {
ItemBindings.Remove(itemToRemove);
}
даже itemToRemove
имеет то же самое hashCode
как предмет из коллекции.
Также я попытался найти элемент в коллекции, сохранить его в переменной и удалить его:
private void OnRemove(ItemBinding itemToRemove) {
var foundItem = ItemBindings.Single( x => x.Id == itemToRemove.Id); // always if found
ItemBindings.Remove(foundItem);
}
но это не работает
Обходной путь, который работает хорошо, это:
private void OnRemove(ItemBinding itemToRemove) {
var backUpItems = new List<ItemBinding>(ItemBindings);
backUpItems.Remove(itemToRemove);
ItemBindings.Clear();
ItemBindings.AddAll(backUpItems);
}
но это грязный обходной путь. Я пытаюсь сделать это просто Удалить элегантно:).
ИЗМЕНИТЬ ТИП
Если я меняю тип с ISet в IList, он работает нормально.
public IList<ItemBinding> ItemBindings { get; set; }
ItemBindings = new List<ItemBinding>();
Когда я хочу удалить элемент из этой коллекции, он удаляется.
private void OnRemove(ItemBinding itemToRemove) {
ItemBindings.Remove(itemToRemove);
}
Чего мне не хватает в том, что я не могу удалить элементы из ISet...?
Спасибо за предложения, решения.
3 ответа
Это очень простая проблема. Просто скачайте dotPeek 1.2 и запустите сервер символов, после чего вы можете проверить НАСТОЯЩУЮ реализацию ISet.Remove() и понять, почему она привередлива. Как сказал @HansPassant, это, вероятно, случай GetHashCode(), или фактическая реализация HashedSet
Что касается моих предположений; Взгляни на DictionarySet
(базовый класс HashedSet
):
Как вы можете видеть, Remove() использует Contains() для проверки, должен ли он удалять элемент или нет. Что делает Contains()? В основном это обертка вокруг Dictionary.Contains()
http://msdn.microsoft.com/en-us/library/ms182358(v=vs.80).aspx
GetHashCode возвращает значение, основанное на текущем экземпляре, которое подходит для алгоритмов хеширования и структур данных, таких как хеш-таблица. Два объекта одинакового типа и одинаковые должны возвращать один и тот же хэш-код, чтобы гарантировать правильную работу экземпляров System.Collections.HashTable и System.Collections.Generic.Dictionary.
Обратите внимание, что важно, чтобы:
1) Ваш GetHashCode() не может измениться. Это означает, что все поля, которые используются GetHashCode(), не могут быть изменены. Как только вы вставите элемент в Dictionary, будет вызван его GetHashCode(), и он будет помещен в конкретный сегмент. Вы не сможете восстановить его, если позже у вас будет другой GetHashCode(). После того, как GetHashCode() пройдет, будет вызван ваш метод Equals. Убедитесь, что ваши поля неизменны.
Соответствующий источник из словаря:
private int FindEntry(TKey key)
{
if ((object) key == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
if (this.buckets != null)
{
int num = this.comparer.GetHashCode(key) & int.MaxValue;
for (int index = this.buckets[num % this.buckets.Length]; index >= 0; index = this.entries[index].next)
{
if (this.entries[index].hashCode == num && this.comparer.Equals(this.entries[index].key, key))
return index;
}
}
return -1;
}
Посмотрите этот поток, как переопределить Equals() И GetHashCode():
Почему важно переопределить GetHashCode, если переопределен метод Equals?
Обратите внимание на ответ @Albic в этой теме.
Вы можете попробовать использовать...
private void OnRemove(ItemBinding itemToRemove)
{
ItemBindings.RemoveWhere(x => x.Id == itemToRemove.Id);
}
Я не могу воспроизвести это поведение.
private class ItemBinding
{
public string ID { get; set; }
}
[TestMethod]
public void TestMethod1()
{
System.Collections.Generic.HashSet<ItemBinding> set = new System.Collections.Generic.HashSet<ItemBinding>();
ItemBinding item1 = new ItemBinding() { ID = "Jaffa" };
set.Add(item1);
Assert.IsTrue(set.Count == 1);
set.Remove(item1);
Assert.IsTrue(set.Count == 0);
ItemBinding item2 = new ItemBinding() { ID = "Moon" };
set.Add(item2);
ItemBinding item3 = new ItemBinding() { ID = "Moon" };
Assert.IsTrue(item2.GetHashCode() != item3.GetHashCode());
Assert.IsTrue(set.Remove(item3) == false);
Assert.IsTrue(set.Count == 1);
}
Вышеприведенный тест показывает, что Hashset работает должным образом. Возможно ли, что вы попали в ловушку, показанную во втором тесте, по сравнению двух экземпляров класса, которые имеют одинаковые значения, но на самом деле являются разными экземплярами класса (следовательно, не прошли тест на равенство GetHashCode?).
Если вы можете изменить опубликованный код здесь, чтобы более точно представить вашу конкретную проблему, это было бы полезно.