I have an entity class like this (with lots of stuff missing):
class Parent
{
private readonly Iesi.Collections.Generic.ISet<Child> children =
new Iesi.Collections.Generic.HashedSet<Child>();
public virtual void AddChild(Child child)
{
if (!this.children.Contains(child))
{
this.children.Add(child);
child.Parent = this;
}
}
public virtual void RemoveChild(Child child)
{
if (this.children.Contains(child))
{
child.Parent = null;
this.children.Remove(child);
}
}
}
However, when I attempt to remove a child, the if
statement evaluates to false
. So, I put a breakpoint on the if
statement, and evaluated certain expressions:
this.children.Contains(child) => false
this.children.ToList()[0].Equals(child) => true
this.children.ToList()[0].GetHashCode() => 1095838920
child.GetHashCode() => 1095838920
My understanding is that if GetHashCode
returns identical values, it then checks Equals
. Why is Contains
returning false?
Both of my Parent
and Child
entities inherit from a common Entity
base class, which is a non-generic version of the generic entity base class from page 25 of the NHibernate 3.0 Cookbook. Here is my base class:
public class Entity : IEntity
{
public virtual Guid Id { get; private set; }
public override bool Equals(object obj)
{
return Equals(obj as Entity);
}
private static bool isTransient(Entity obj)
{
return obj != null &&
Equals(obj.Id, Guid.Empty);
}
private Type getUnproxiedType()
{
return GetType();
}
public virtual bool Equals(Entity other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (!isTransient(this) &&
!isTransient(other) &&
Equals(Id, other.Id))
{
var otherType = other.getUnproxiedType();
var thisType = getUnproxiedType();
return thisType.IsAssignableFrom(otherType) ||
otherType.IsAssignableFrom(thisType);
}
return false;
}
public override int GetHashCode()
{
if (Equals(Id, Guid.Empty))
return base.GetHashCode();
return Id.GetHashCode();
}
}
After further investigation, I feel something like this is happening:
parent.AddChild(child)
child.Id
to be generated parent.RemoveChild(child)
...and as discussed below, this was changing GetHashCode()
.
This was the result of a bug in my program - I was supposed to reload parent
between steps 2 and 3.
Still, I think there's something more fundamentally wrong.
我想不出有任何其他方式可能发生这种情况 - Iesi.Collections.Generic.HashedSet
必须包含自己的Contains
,其行为与我们预期的不同。
Does Child
both override object.Equals(object)
and implement IEquatable<Child>
? It's possible that the equality that the collection is doing isn't the same as the Equals
method you're calling on the 2nd line of your code sample.
To get this to work, I had to change my Entity
class' GetHashCode
method to lazy-evaluate the hash code, but once computed, cache the result and never let it change. Here is my new implementation of GetHashCode
:
private int? requestedHashCode;
public override int GetHashCode()
{
if (!requestedHashCode.HasValue)
{
requestedHashCode = isTransient(this)
? base.GetHashCode()
: this.Id.GetHashCode();
}
return requestedHashCode.Value;
}
For a better implementation of a base entity class, see AbstractEntity .
This could happen if Equals(Child)
is implemented differently to the override of Equals(object)
. It would really depend on what Child
looked like.
It can also happen due to the effect that Henk mention - is Parent
part of the calculation of the hash code and equality, for example? If so, setting Parent
to null
will probably change the hash code of the child to be one other than the hash code which is recorded in the HashSet
. It's not a problem if Parent
isn't part of the equality/hash calculation, although it's still somewhat odd to put a mutable type into a hash set or use it as the key in a hash table.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.