具有对象代理对象和等于的 C# 集合
C# collection with object proxy objects and Equals
我在将 .NET 集合(List 和 HashSet)与具有重写的 Equals 方法的对象一起使用时遇到问题。
上下文(代码如下):
- 我有一个基础 class,我在其中通过调用另一个方法(Equals(EntityBase<>) 或使用完全不同的名称来比较这两个对象的 ID 来实现 Equals(object)。
- 我有一个具体的 class 派生自基础 class。
- 我创建了一个集合(基本上是另一个对象的导航 属性)。
- 在测试中,我在 NSubstitute(内部使用 Castle)的帮助下创建对象代理,但在现实世界中它可以是 EntityFramework 或 NHibernate 代理。
- 将对象放入集合后,集合找不到它或其他具有相同id的对象。
- 我无法从集合中删除该对象。
作为解决方法,我不得不在 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<> 应该使用 Substitute.ForPartsOf<>.
当使用 For<> 创建代理时,它会覆盖所有可以覆盖的方法。在这种情况下,Equals(EntityBase<>) 的实现是空的,调试器无法进入该方法。
当对象与 ORM 一起使用时,从方法声明中删除 virtual 关键字的选项可能不起作用,ORM 在每个方法上都需要 virtual 和 属性(我的意思是 NHibernate)。
请查收
[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));
}
}
现在测试通过
坑点是省略了public bool Equals(EntityBase<TId> other)
中的virtual
语句。可能 NSubstitute 代理有自己的方法实现。
我在将 .NET 集合(List 和 HashSet)与具有重写的 Equals 方法的对象一起使用时遇到问题。
上下文(代码如下):
- 我有一个基础 class,我在其中通过调用另一个方法(Equals(EntityBase<>) 或使用完全不同的名称来比较这两个对象的 ID 来实现 Equals(object)。
- 我有一个具体的 class 派生自基础 class。
- 我创建了一个集合(基本上是另一个对象的导航 属性)。
- 在测试中,我在 NSubstitute(内部使用 Castle)的帮助下创建对象代理,但在现实世界中它可以是 EntityFramework 或 NHibernate 代理。
- 将对象放入集合后,集合找不到它或其他具有相同id的对象。
- 我无法从集合中删除该对象。
作为解决方法,我不得不在 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<> 应该使用 Substitute.ForPartsOf<>.
当使用 For<> 创建代理时,它会覆盖所有可以覆盖的方法。在这种情况下,Equals(EntityBase<>) 的实现是空的,调试器无法进入该方法。
当对象与 ORM 一起使用时,从方法声明中删除 virtual 关键字的选项可能不起作用,ORM 在每个方法上都需要 virtual 和 属性(我的意思是 NHibernate)。
请查收
[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));
}
}
现在测试通过
坑点是省略了public bool Equals(EntityBase<TId> other)
中的virtual
语句。可能 NSubstitute 代理有自己的方法实现。