C# 集合与 NSubstitue 代理对象覆盖等于

C# collection with NSubstitue proxy objects overwrite Equals

我在将 .NET 集合与 Nsubstitue 对象一起使用时遇到问题。

  1. 我有一个基础 class,我在其中实现了 Equals(object)、CompareTo 函数
  2. 在测试中,我为此基础创建了两个精确的 Nsubstitue 对象代理 class。
  3. 我把对象放入集合后,集合显示这两个对象代理是两个不同的对象。

我想知道这种行为的原因是什么,以及如何使用模型定义集合。

public class KeyTestClass : IKeyTestClass
{
    public int Id { get; private set; } 

    public KeyTestClass()
    {
        Id = 1;
    }

    public override int GetHashCode()
    {
        return Id;
    }


    public int CompareTo(IKeyTestClass other)
    {
        return Id - other.Id;
    }

    public bool Equals(IKeyTestClass other)
    {
        return Id == other.Id;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != GetType()) return false;
        return Equals((KeyTestClass)obj);
    }


}

public interface IKeyTestClass : IComparable<IKeyTestClass>, IEquatable<IKeyTestClass>
{
    int Id { get; }
}

public class KeyTestClass2 : IKeyTestClass2
{

}

public interface IKeyTestClass2
{

} 

[TestClass]
public class ConsistencyRelatedTests
{

    [TestMethod]
    public void ValidateTestClass()
    {

        var dic = new Dictionary<IKeyTestClass,List<IKeyTestClass2>>();

        // using new equals function defined by Nsubstittue.
        var item1 = Substitute.For<IKeyTestClass>();
        var item2 = Substitute.For<IKeyTestClass>();
        item1.Id.Returns(1);
        item2.Id.Returns(1);

        Assert.IsTrue(item1.Equals(item2)); //false, not working
        dic.Add(item1, new List<IKeyTestClass2>());
        dic.Add(item2, new List<IKeyTestClass2>());

        // Using real class equals method
        var item3 = new KeyTestClass();
        var item4 = new KeyTestClass();
        Assert.IsTrue(item3.Equals(item4)); //working
        dic.Add(item3, new List<IKeyTestClass2>());
        dic.Add(item4, new List<IKeyTestClass2>());
    }

}

在此示例中,Equals() 是为 KeyTestClass 实现的。这并不意味着它适用于 IKeyTestClass 的所有实例。如果我们创建另一个 class KeyTestClass2 : IKeyTestClass 并使用默认 Equals 我们会得到完全相同的结果:

var item1 = new KeyTestClass2();
var item2 = new KeyTestClass2();
Assert.IsTrue(item1.Equals(item2)); // also fails, as expected

这几乎就是 NSubstitute 在我们调用 Substitute.For<IKeyTestClass>() 时所做的事情。它创建了一个新的 class 实现接口,因此它完全不知道 KeyTestClass.Equals 实现。

这里有几个选项,具体取决于您要执行的操作。首先,您的 KeyTestClass.Equals 适用于所有 IKeyTestClass 实例,包括替代品,因此您可以很好地使用它:

var item1 = new KeyTestClass(); 
var item2 = Substitute.For<IKeyTestClass>();
var item3 = Substitute.For<IKeyTestClass>();
item2.Id.Returns(1);
item3.Id.Returns(42);
Assert.IsTrue(item1.Equals(item2)); // Passes (as expected)
Assert.IsFalse(item1.Equals(item3)); // Passes (as expected)

其次,您可以用 IEquatable<IKeyTestClass>.Equals 方法替换替代品(注意:不适用于 Object.Equals,即 can cause problems)。

var item1 = Substitute.For<IKeyTestClass>();
var item2 = Substitute.For<IKeyTestClass>();
item1.Id.Returns(1);
item2.Id.Returns(1);
item1.Equals(item2).Returns(true);

Assert.IsTrue(item1.Equals(item2)); // Passes as expected

在这种情况下,您可能还想删除 item2.Equals(item1)。我们还可以使用 item1.Equals(Arg.Any<IKeyTestClass>).Returns(x => KeyTestClass.Equals(item1, x.Arg<IKeyTestClass>()) 委托给真正的逻辑(需要提取 Equals 逻辑的 static 版本),并使用此调用配置每个替代。

要考虑的另一个选择是尽可能在此处使用真实的 KeyTestClass(或手动编码的测试替身)。由于所有内容都回退到 Object.Equals,平等在 .NET 中可能有点棘手,因此当平等是契约的关键部分时,需要稍微小心地消除这一点。