Hashset not adding a duplicate, but returning true for Add()
(edit) more info. First notice "new virtual". This class inherits a base class which is supposed to be a generic parent-aware class that can be created with any ICollection type. Here's the descriptor, basically:
public abstract class ParentAwareCollection<TObject, TParent, TInnerList> :
ICollection<TObject>, ICollection, IParentProvider<TParent>
where TObject : IParentProvider<TParent>
where TInnerList : ICollection<TObject>, new()
{
protected TInnerList InnerList = new TInnerList();
...
}
This class:
public class ParentAwareHashset<TObject, TParent> :
ParentAwareCollection&l开发者_StackOverflow中文版t;TObject, TParent, HashSet<TObject>>, ISet<TObject>,
ICollection where TObject : IParentProvider<TParent>
{
public ParentAwareHashset(TParent parent)
: base(parent)
IParentProvider just requires that a "ParentCollection" object be present.
In debugging, the overridden (void) Add method in ParentAwareCollection
is never called. So I don't think that the "new" is the problem.
Also, here's something that I also don't understand. Here's the descriptor for an actual HashSet:
public class HashSet<T> : ISerializable, IDeserializationCallback,
ISet<T>, ICollection<T>, IEnumerable<T>, IEnumerable
{
...
public bool Add(T item);
}
Notice that in implements ICollection just like mine. That is what led me to believe that this was OK to do. However unlike mine, the framework HashSet does not use a new
descriptor for its Add() method, but that's required because ICollection<T>
implements void Add(T item)
. Perhaps they just omitted it?
Original question:
I have a collection class that uses a HashSet<T>
to store its objects. The objects of type T override GetHashCode()
, in which they make sure that certain required information is present that is used to produce the hash code. I am not sure if this is important.
What happens is that when I do an Add(T) to the HashSet, it does not add the object, but returns true. If I debug and then in the immediate window try to add it again, it returns false, like it's supposed to. The method looks like this:
public new virtual bool Add(TObject item)
{
// Must add parent first, since it may be used in the hash code
// InnerList is a HashSet<T>
if (InnerList.Any(existing=>item.GetHashCode()==existing.GetHashCode())) {
return(false);
} else {
if (InnerList.Add(item))
{
return(true);
} else {
return(false);
}
}
}
I added the first condition to make my code actually work. It works as expected with this there. However, I can't understand why I would have to do this, and I can think of no reason, ever, why a HashSet would return "true" for Add() without adding anything. Even if my GetHashCode override is messed up, it should either add it and return true, or not and return false. Any thoughts?
Here's what I observed while debugging:
Breaking before InnerList.Add():
?InnerList.Count
1
?item.GetHashCode()
-1629834529
?InnerList.ElementAt(0).GetHashCode()
-1629834529
Step over the InnerList.Add(), which returns true:
?InnerList.Count
1
?InnerList.Add(item)
false
Wtf? This is .net 4.0 framework.
Your Add
code is wrong:
public new virtual bool Add(TObject item)
{
// Must add parent first, since it may be used in the hash code
// InnerList is a HashSet<T>
if (InnerList.Any(existing=>item.GetHashCode()==existing.GetHashCode())) {
return(false);
} else {
if (InnerList.Add(item))
{
return(true);
} else {
return(false);
}
}
}
Two different items can have the same hash code! Your function should look like this:
public new virtual bool Add(TObject item)
{
return InnerList.Add(item);
}
I figure it out. This is a very insidious little thing. So the whole point of this setup is so that I can have collection classes that have a "Parent", and when you add items to the collection, they are automatically assigned that Parent. (This Parent is not simply the class itself, it's another object).
Here is the problem:
CsmResourceHashSet resources = new CsmResourceHashSet(Context);
resources.AddFrom(Context.ScriptResources
.Where(item=>item.Enabled));
CsmResourceHashSet
is a ParentAwareHashset
. Extension method AddFrom:
public static void AddFrom<T>(this ICollection<T> destList, IEnumerable<T> sourceList)
{
foreach (T obj in sourceList)
{
destList.Add(obj);
}
}
so it's apparently using the ICollection
implementation to perform the Add. The void Add
method of the base ParentAwareCollection
is being used.
I guess it is casting my object to the base class when it uses this, even though I have a 'new Add
. It seems weird to me that when you override a method with new, that an instance of that object could still have it's base method operated on externally.
@Anthony Pegram's comment actually enabled me to find the solution. So what was happening is that the void Add() was being called, which consequently called the actual derived InnerList Add(), which was indeed returning false, but that value was never getting back to code that needed to know about it.
The effect was that the HashSet actually had the correct contents, but Parent didn't get set for the one that got rejected from the HashSet. So this made the code work almost all the time... except when it mattered that the code didn't know something had been rejected. But without the "Equals" check, also, I was permitting duplicates of things that I shouldn't have been.
Solution was to add another ExtensionMethod for ISet<>
public static void AddFrom<T>(this ISet<T> destList, IEnumerable<T> sourceList)
{
foreach (T obj in sourceList)
{
destList.Add(obj);
}
}
Once I did that, it works. Well, actually it didn't work, but it revealed a couple crazy little bugs that mostly didn't manifest themselves, but all my exceptions started blowing up everywhere and it took no time to fix them all.
精彩评论