我在使用带有重写的Equals方法的对象的.NET集合(List和HashSet)时遇到了问题。
上下文(代码如下):
作为一种解决方法,我不得不在Equals方法中复制Equals(EntityBase<>)的代码,这解决了这个问题。
环境:VS2013 SP4,NUnit,NSubstitute,.NET Framework 4.5.1。
我想知道当从Equals调用另一个方法时,这种行为可能是什么原因。
代码:
[TestFixture]
public class ObjectEqualsTests
{
[Test]
public void CollectionAddRemoveEntityTest()
{
const int id = 12345;
var list = new List<MyObject>();
var firstObject = Substitute.For<MyObject>();
firstObject.Id.Returns(id);
list.Add(firstObject);
Assert.IsTrue(list.Contains(firstObject), "Cannot find the first object");
var secondObjectWithSameId = Substitute.For<MyObject>();
secondObjectWithSameId.Id.Returns(id);
Assert.IsTrue(list.Contains(secondObjectWithSameId), "Cannot find the second object");
list.Remove(secondObjectWithSameId);
Assert.AreEqual(0, list.Count, "Object was not removed from the list");
}
}
public class MyObject : EntityBase<int>
{
public virtual string SomeProperty { get; set; }
}
public interface IEntity<TId>
{
TId Id { get; }
}
public class EntityBase<TId> : IEntity<TId>, IEquatable<EntityBase<TId>>
{
public virtual TId Id { get; protected set; }
public override bool Equals(object obj)
{
var other = obj as EntityBase<TId>;
// if to remove next two lines, the test passes
return Equals(other); // the first implementation
return EqualsWithDifferentName(other); // first attempt to fix
// second attempt to fix by duplicating the code
if (other == null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
if (!IsTransient(this) && !IsTransient(other) && Equals(Id, other.Id))
{
var otherType = other.GetUnproxfiedType();
var thisType = GetUnproxfiedType();
return thisType.IsAssignableFrom(otherType) && otherType.IsAssignableFrom(thisType);
}
return false;
}
private Type GetUnproxfiedType()
{
return GetType();
}
private static bool IsTransient(EntityBase<TId> obj)
{
return obj != null && Equals(obj.Id, default(TId));
}
public virtual bool Equals(EntityBase<TId> other)
{
if (other == null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
if (!IsTransient(this) && !IsTransient(other) && Equals(Id, other.Id))
{
var otherType = other.GetUnproxfiedType();
var thisType = GetUnproxfiedType();
return thisType.IsAssignableFrom(otherType) && otherType.IsAssignableFrom(thisType);
}
return false;
}
public virtual bool EqualsWithDifferentName(EntityBase<TId> other)
{
if (other == null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
if (!IsTransient(this) && !IsTransient(other) && Equals(Id, other.Id))
{
var otherType = other.GetUnproxfiedType();
var thisType = GetUnproxfiedType();
return thisType.IsAssignableFrom(otherType) && otherType.IsAssignableFrom(thisType);
}
return false;
}
public override int GetHashCode()
{
return Equals(Id, default(TId)) ? base.GetHashCode() : Id.GetHashCode();
}
}
更新: 而不是使用Substitute.For&lt;&gt;一个人应该使用Substitute.ForPartsOf&lt;&gt;。
使用For&lt;&gt;创建代理时它会覆盖所有可以覆盖的方法。在这种情况下,Equals(EntityBase&lt;&gt;)具有空实现,并且调试器无法进入此方法。
当对象与ORM一起使用时,从方法声明中删除虚拟关键字的选项可能不起作用,ORM需要在每个方法和属性上使用虚拟(我的意思是NHibernate)。
答案 0 :(得分:0)
请检查
[TestFixture]
public class ObjectEqualsTests
{
[Test]
public void CollectionAddRemoveEntityTest()
{
const int id = 12345;
var list = new List<MyObject>();
var firstObject = Substitute.For<MyObject>();
firstObject.Id.Returns(id);
list.Add(firstObject);
Assert.IsTrue(list.Contains(firstObject), "Cannot find the first object");
var secondObjectWithSameId = Substitute.For<MyObject>();
secondObjectWithSameId.Id.Returns(id);
Assert.IsTrue(list.Contains(secondObjectWithSameId), "Cannot find the second object");
list.Remove(secondObjectWithSameId);
Assert.AreEqual(0, list.Count, "Object was not removed from the list");
}
}
public class MyObject : EntityBase<int>
{
public virtual string SomeProperty { get; set; }
}
public interface IEntity<TId>
{
TId Id { get; }
}
public class EntityBase<TId> : IEntity<TId>
{
public virtual TId Id { get; protected set; }
public override bool Equals(object other)
{
EntityBase<TId> otherEntity = other as EntityBase<TId>;
if ((object)otherEntity == null)
{
return false;
}
return Equals(otherEntity);
}
public bool Equals(EntityBase<TId> other)
{
if (other == null)
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
if (!IsTransient(this) && !IsTransient(other) && Equals(Id, other.Id))
{
var otherType = other.GetUnproxfiedType();
var thisType = GetUnproxfiedType();
return thisType.IsAssignableFrom(otherType) && otherType.IsAssignableFrom(thisType);
}
return false;
}
public override int GetHashCode()
{
return Equals(Id, default(TId)) ? base.GetHashCode() : Id.GetHashCode();
}
private Type GetUnproxfiedType()
{
return GetType();
}
private static bool IsTransient(EntityBase<TId> obj)
{
return obj != null && Equals(obj.Id, default(TId));
}
}
现在测试通过
要点是virtual
中省略public bool Equals(EntityBase<TId> other)
语句。可能NSubstitute代理有自己的方法实现。