带有对象代理对象和Equals的C#集合

时间:2015-01-23 09:16:09

标签: c# list proxy equals nsubstitute

我在使用带有重写的Equals方法的对象的.NET集合(List和HashSet)时遇到了问题。

上下文(代码如下):

  1. 我有一个基类,我通过调用另一个方法(Equals(EntityBase<>))或者使用完全不同的名称来实现Equals(object),我在这里比较了这两个对象的ID。
  2. 我有一个从基类派生的具体类。
  3. 我创建了一个集合(基本上是另一个对象的导航属性)。
  4. 在测试中,我在NSubstitute的帮助下创建了对象代理(内部使用Castle),但在现实世界中,它可以是EntityFramework或NHibernate代理。
  5. 将对象放入集合后,集合无法找到它或具有相同ID的其他对象。
  6. 我无法从集合中删除该对象。
  7. 作为一种解决方法,我不得不在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)。

1 个答案:

答案 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代理有自己的方法实现。