为什么这个对象相等性测试会失败?

3

请参加以下课程和单元测试。

public class Entity
    {
        public object Id { get; set; }

        public override bool Equals(object obj)
        {
            return this == (Entity)obj;
        }

        public static bool operator == (Entity base1, Entity base2)
        {
            if (base1.Id != base2.Id)
            {
                return false;
            }

            return true;
        }

        public static bool operator != (Entity base1, Entity base2)
        {
            return (!(base1.Id == base2.Id));
        }
    }

        [TestMethod]
        public void Test()
        {
            Entity e1 = new Entity { Id = 1 };
            Entity e2 = new Entity { Id = 1 };
            Assert.IsTrue(e1 == e2); //Always fails
        }

有人能解释一下为什么它失败了吗?

8个回答

8

你的Id属性是一个object类型。当你使用1作为每个实例的Id来构造两个实例时,你将得到两个不同的装箱对象。然后你使用引用相等性比较这些对象。

建议进行以下更改以解决此问题:

  • 如果适用,将Id的类型更改为int
  • 使用静态的object.Equals方法来比较Ids,而不是使用==。

这两种方法都可以解决问题,但我认为第一种更好。

如果你有兴趣,还有其他几种实现方法可以使代码更清晰。以下是快速浏览的列表:

  • You should be overriding GetHashCode as well as Equals.
  • Your Equals override shouldn't perform a cast unconditionally, as that will throw an exception if the object is of the wrong type. If the type is wrong, return false.
  • Your current == implementation can be simplified to just

    return base1.Id == base2.Id;
    
  • Your == implementation should perform nullity checks
  • It's generally best to implement != by returning !(base1 == base2) unless you want specialist behaviour.
  • Overriding Equals in a non-sealed class can be problematic. Unless you're planning for inheritance, it would be worth sealing the class (IMO - this will probably be controversial).

我遇到这个问题的原因是因为我试图在Dictionary<Entity,int>中使用Entity并检索Key。那么如果我想在Dictionary类中使用Entity,我是否被迫将类型更改为int? - Th3Fix3r
如果您正在尝试将其用作字典键,则绝对应该覆盖GetHashCode。这绝对是至关重要的。不,您不必强制更改Id类型为int,但应该使用Equals来比较Ids,而不是==。您可以在GetHashCode的实现中使用Id的哈希码。 - Jon Skeet

3
由于您依赖于对象引用进行比较,因此:
public object Id { get; set; }

替换

    public static bool operator == (Entity base1, Entity base2)
    {
        if (base1.Id != base2.Id)
        {
            return false;
        }

        return true;
    }

随着

    public static bool operator == (Entity base1, Entity base2)
    {
        return object.Equals(base1.Id, base2.Id);
    }

我更喜欢使用object.Equals(base1.Id, base2.Id)来处理空的Id。 - Jon Skeet
当然,这仍然存在base1或base2为空的问题,但这也涉及到其他问题 :) - Jon Skeet

2
因为e1.Id和e2.Id是不同的对象。即使它们具有相同的值,它们也不是相同的对象,因此base1.Id == base2.Id失败。

好的,如果我将我的Id更改为int类型,它应该可以工作...无法覆盖对象类型的==操作符吗? - Th3Fix3r
1
你从不会覆盖操作符,你只会重载它们。通常你会覆盖Equals方法。 - Jon Skeet
这是因为Id的编译时类是一个对象,所以它使用默认的引用相等性。==和!=并不是以你期望的方式虚拟化的,决定使用的==方法是在<i>编译</i>时确定的。 - thecoop

2
你的equals实现只是比较引用,而不是对象的内容。你本质上是在比较两个指针,因为你创建了两个独立的对象,所以你的相等性将会失败。
有几篇关于如何正确实现Equals的文章,这是一个开始:

http://weblogs.asp.net/tgraham/archive/2004/03/23/94870.aspx

对于数据库实体,你可以通过仅比较数据库ID来实现快捷的等于(Equals)实现,假设在你的系统中具有相同ID的两个对象被视为“相等”。

2

您的 Id 是一个对象,而不是 int。对于对象,== 运算符不会检查值的相等性。


1

因为您的 Id 属性是一个对象。

1(作为 int)被装箱成堆对象,但每个 1 都被装箱成单独的实例。由于 Id 是一个对象,base1.Id != base2.Id 条件检查的是引用相等性而不是相等性,这不是您想要的。将 Id 更改为 int,或使用 Equals() 而不是 != 应该可以解决问题。


0
如果你将你的Id成员变量设置为int类型,它就可以工作了。但正如CookieOfFortune所说,当你比较两个对象时,它会查看它们是否是完全相同的对象,而不是它们是否具有相同的值。

0
将Id的类型更改为int或其他值类型。问题在于你正在比较2个对象,我假设你重载了==运算符来解决这个问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接