等于覆盖和 == 重载,对于值对象与实体
Equals override and == overload, for value objects vs entities
我发现很多关于 Equals 覆盖和 == 运算符重载之间的区别的讨论,但是对于我们何时应该或不覆盖默认的相等行为似乎存在一些分歧,这让我怀疑这样做的好方法它。
以下是我的理解,如有不妥之处请指正:
1) 不建议对非不可变类型使用 == 重载(为什么??),并且对于不可变类型(又名 DDD 的值对象)很有用,使得 == 比较 return true 如果值相同但参考文献不同。
2) 在不可变类型中也应覆盖 Equals(和 GetHashCode),以便对类型内相关字段的每个值进行良好比较。
3) 实体的 Equals 怎么样?
覆盖 Equals 并只比较 id 属性是个好主意吗?或者我应该让比较引用的默认对象行为?
对于这两个选项,我认为如果我遵循一条规则,即在线程上下文中我应该始终只有一个特定实体的实例,结果应该是相同的,但是有什么缺点或优点吗我应该注意这些选项之一?
动机
在我看来,正确的相等操作是面向对象世界中最被低估的工具之一。是的,你绝对应该在有意义的地方实现它们,它会让你的程序更加简洁。
例如比较
Assert.Equal(expectedAddress.Street, address.Street);
Assert.Equal(expectedAddress.City, address.City);
Assert.Equal(expectedAddress.Zip, address.Zip);
Assert.Equal(expectedAddress.State, address.State);
Assert.Equal(expectedAddress.Country, address.Country);
和
Assert.Equal(expectedAddress, address);
当你有深度嵌套的值对象时,这会变得更加极端。
何时使用
为了不产生尴尬的行为,只对不可变类型实现相等操作。这很重要,因为例如哈希映射将无法与可变类型一起正常工作(想想当对象的哈希码在哈希映射中发生变化时会发生什么)。
单独实现 Equals
对于某些可变类型可能有意义,但通常不鼓励,例如通过 Microsoft code analysis rule。
值对象
相等运算对值对象最有用。还重写相等运算符以使相等比较看起来自然。
相等运算的实现很简单:考虑所有数据字段但忽略计算属性。这将创建纯粹基于内容的相等操作。
由于在值对象上实现相等操作是机械的,因此有一个 library called Equ 可以自动为您执行此操作(这是我自己编写的)。它将在静态实例化时创建相等操作,与手动编写的 Equals
和 GetHashCode
实现具有相同的运行时性能。
实体
对于实体,它变得有点棘手。问题是从领域的角度.
,通常并不清楚相等的含义。
显然,两个 Customer
个 ID 不同的实体是不相等的。但仅此而已。具有相同 ID 但处于不同状态的两个 Customer
实体是否相等?一道难题。
好消息是这种功能并不是真正需要的。所以我的建议是:不要对实体实施相等操作。
我发现很多关于 Equals 覆盖和 == 运算符重载之间的区别的讨论,但是对于我们何时应该或不覆盖默认的相等行为似乎存在一些分歧,这让我怀疑这样做的好方法它。
以下是我的理解,如有不妥之处请指正:
1) 不建议对非不可变类型使用 == 重载(为什么??),并且对于不可变类型(又名 DDD 的值对象)很有用,使得 == 比较 return true 如果值相同但参考文献不同。
2) 在不可变类型中也应覆盖 Equals(和 GetHashCode),以便对类型内相关字段的每个值进行良好比较。
3) 实体的 Equals 怎么样?
覆盖 Equals 并只比较 id 属性是个好主意吗?或者我应该让比较引用的默认对象行为?
对于这两个选项,我认为如果我遵循一条规则,即在线程上下文中我应该始终只有一个特定实体的实例,结果应该是相同的,但是有什么缺点或优点吗我应该注意这些选项之一?
动机
在我看来,正确的相等操作是面向对象世界中最被低估的工具之一。是的,你绝对应该在有意义的地方实现它们,它会让你的程序更加简洁。
例如比较
Assert.Equal(expectedAddress.Street, address.Street);
Assert.Equal(expectedAddress.City, address.City);
Assert.Equal(expectedAddress.Zip, address.Zip);
Assert.Equal(expectedAddress.State, address.State);
Assert.Equal(expectedAddress.Country, address.Country);
和
Assert.Equal(expectedAddress, address);
当你有深度嵌套的值对象时,这会变得更加极端。
何时使用
为了不产生尴尬的行为,只对不可变类型实现相等操作。这很重要,因为例如哈希映射将无法与可变类型一起正常工作(想想当对象的哈希码在哈希映射中发生变化时会发生什么)。
单独实现 Equals
对于某些可变类型可能有意义,但通常不鼓励,例如通过 Microsoft code analysis rule。
值对象
相等运算对值对象最有用。还重写相等运算符以使相等比较看起来自然。
相等运算的实现很简单:考虑所有数据字段但忽略计算属性。这将创建纯粹基于内容的相等操作。
由于在值对象上实现相等操作是机械的,因此有一个 library called Equ 可以自动为您执行此操作(这是我自己编写的)。它将在静态实例化时创建相等操作,与手动编写的 Equals
和 GetHashCode
实现具有相同的运行时性能。
实体
对于实体,它变得有点棘手。问题是从领域的角度.
,通常并不清楚相等的含义。显然,两个 Customer
个 ID 不同的实体是不相等的。但仅此而已。具有相同 ID 但处于不同状态的两个 Customer
实体是否相等?一道难题。
好消息是这种功能并不是真正需要的。所以我的建议是:不要对实体实施相等操作。