2

I have a collection of type:
Iesi.Collections.Generic

public ISet<ItemBinding> ItemBindings { get; set; }

where ItemBinding is Domain.Model

I initialize the collection in this way:

ItemBindings = new HashedSet<ItemBinding>();

and I fill the collection with members.

When i want to remove an item from this collection i can't remove it.

private void OnRemove(ItemBinding itemToRemove) {
    ItemBindings.Remove(itemToRemove);
}

even the itemToRemove has the same hashCode as the item from the collection.

Also I tried in to find the item in collection, keep it in a variable, and remove it:

private void OnRemove(ItemBinding itemToRemove) {
    var foundItem = ItemBindings.Single( x => x.Id == itemToRemove.Id); // always if found
    ItemBindings.Remove(foundItem);
 }

but this doesn't work.

An workaround which works ok is this:

private void OnRemove(ItemBinding itemToRemove) {
    var backUpItems = new List<ItemBinding>(ItemBindings);
    backUpItems.Remove(itemToRemove);

    ItemBindings.Clear();
    ItemBindings.AddAll(backUpItems);
 }

but this is an dirty workaround. I'm trying to do this simple Remove in an elegant manner :).

CHANGE the TYPE

If I change the type from ISet in IList it works ok.

public IList<ItemBinding> ItemBindings { get; set; }
ItemBindings = new List<ItemBinding>();

When i want to remove an item from this collection IT IS REMOVED.

private void OnRemove(ItemBinding itemToRemove) {
    ItemBindings.Remove(itemToRemove);
}

What i'm missing in the way that i can't remove items from ISet ... ?

Thank you for suggestions, solutions.

mihai
  • 2,746
  • 3
  • 35
  • 56
  • someone encountered such a problem ? – mihai Jun 16 '14 at 11:53
  • We have had the same problem, the change to IList is working for us! – lxalln Jul 28 '14 at 13:49
  • What value does HashSet.Remove() return - if the object to remove exists and has been removed, it should return TRUE, or FALSE if the item cannot be found. Which is it in your case ? – PhillipH Jul 28 '14 at 14:03
  • does this question / answer help at all? http://stackoverflow.com/questions/13591543/cant-add-item-to-iesi-collections-generic-iset – DLeh Jul 28 '14 at 14:05
  • Clearly there's a problem with the object's GetHashCode and Equals implementation. There is a difference, HashSet.Remove() uses GetHashCode, List.Remove() does not. "Domain.Model" is too unspecific to have a guess. – Hans Passant Jul 28 '14 at 14:45
  • This may be the cause - http://weblogs.asp.net/ricardoperes/nhibernate-pitfalls-sets-and-hash-codes – Russ Cam Jul 28 '14 at 14:58
  • Thank you for excelent answers and comments. – mihai Aug 05 '14 at 07:43

3 Answers3

2

This is very simple problem. Just download dotPeek 1.2 and fire up symbol server & you can then check into the ACTUAL implementation of ISet.Remove() and see why it's being picky. As @HansPassant said, it's probably the case of GetHashCode(), or the actual implementation of HashedSet

As for my speculations; take a look at DictionarySet(base class of HashedSet):

https://www.symbolsource.org/Public/Metadata/Default/Project/NHibernate/3.0.0.Alpha2/Release/All/Iesi.Collections/Iesi.Collections/Generic/DictionarySet.cs

As you can see, Remove() uses Contains() to actually test if it should remove element or not. What does Contains() do? Basically it's wrapper around Dictionary.Contains()

http://msdn.microsoft.com/en-us/library/ms182358(v=vs.80).aspx

GetHashCode returns a value based on the current instance that is suited for hashing algorithms and data structures such as a hash table. Two objects that are the same type and are equal must return the same hash code to ensure that instances of System.Collections.HashTable and System.Collections.Generic.Dictionary work correctly.

Notice it's important that:

1) Your GetHashCode() can't change. This means that all the fields which are used by GetHashCode() can't change. As soon as you insert element into Dictionary, its GetHashCode() will be called and it's being put to specific bucket. You can't recover it when you have different GetHashCode() later. After the GetHashCode() has passed, your Equals method will be called. Make sure your fields are immutable.

Relevant source from Dictionary:

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;
}

See this thread how to override Equals() AND GetHashCode():

Why is it important to override GetHashCode when Equals method is overridden?

Notice the answer by @Albic, in that thread.

Community
  • 1
  • 1
Erti-Chris Eelmaa
  • 25,338
  • 6
  • 61
  • 78
0

I cannot reproduce this behaviour.

 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);

    }

The above test shows Hashset working as expected. Is is possible you are falling into the trap shown in the second test of comparing two instances of a class that have the same values, but are in fact different class instances (therefore fail the GetHashCode equality test ?).

If you can alter the posted code here to more accurately represent your particular problem, that would be helpful.

PhillipH
  • 6,182
  • 1
  • 15
  • 25
  • You should use `Iesi.Collections.Generic ISet` and concrete implementations, not `System.Collections.Generic.HashSet`. In addition, a test method should persist to DB via NHibernate and load from DB to be representative. – Russ Cam Jul 28 '14 at 14:39
  • Russ, the OP said they used ItemBindings = new HashSet I believed ?. Oh - Ok I see my error, I thought the OP had mistyped HashSet as HashedSet but HashedSet is a concrete implementation in Iesi, not a typo of System.Collections.Hashset. I dont have a copy of NHibernate available to use Reflector/ILSpy on, but thats how I'd go about solving the conundrum. – PhillipH Jul 28 '14 at 15:24
  • I suspect the problem that they are having is related to this - http://weblogs.asp.net/ricardoperes/nhibernate-pitfalls-sets-and-hash-codes – Russ Cam Jul 28 '14 at 15:56
0

You can try using...

private void OnRemove(ItemBinding itemToRemove)
{
    ItemBindings.RemoveWhere(x => x.Id == itemToRemove.Id);
}
Yogesh
  • 14,498
  • 6
  • 44
  • 69