【发布时间】:2018-06-16 07:02:04
【问题描述】:
我有一棵对象树 (DTO),其中一个对象引用其他对象,依此类推:
class Person
{
public int Id { get; }
public Address Address { get; }
// Several other properties
}
public Address
{
public int Id { get; }
public Location Location { get; }
// Several other properties
}
这些对象可能非常复杂并且具有许多其他属性。
在我的应用中,具有相同 Id 的 Person 可能位于两个存储中,即应用中的本地存储和来自后端的存储。我需要以特定方式将在线Person 与本地Person 合并,所以为此我需要首先知道在线Person 是否与本地存储的相同(换句话说,如果本地Person 没有'未被应用更新)。
为了使用 LINQ 的 except,我知道我需要实现 Equatable<T>,我看到的通常方式是这样的:
class Person : IEquatable<Person>
{
public int Id { get; }
public Address Address { get; }
public override bool Equals(object obj)
{
return Equals(obj as Person);
}
public bool Equals(Person other)
{
return other != null &&
Id == other.Id &&
Address.Equals(other.Address);
}
public override int GetHashCode()
{
var hashCode = -306707981;
hashCode = hashCode * -1521134295 + Id.GetHashCode();
hashCode = hashCode * -1521134295 + (Address != null ? Address.GetHashCode() : 0);
return hashCode;
}
对我来说,这听起来很复杂且难以维护,当属性更改时很容易忘记更新Equals 和GetHashCode。
根据对象的不同,它也可能有点计算成本。
以下不是实现Equals 和GethashCode 的更简单有效的方法吗?
class Person : IEquatable<Person>
{
public int Id { get; }
public Address Address { get; private set; }
public DateTime UpdatedAt { get; private set; }
public void SetAdress(Address address)
{
Address = address;
UpdatedAt = DateTime.Now;
}
public override bool Equals(object obj)
{
return Equals(obj as Person);
}
public bool Equals(Person other)
{
return other != null &&
Id == other.Id &&
UpdatedAt.Ticks == other.UpdatedAt.Ticks;
}
public override int GetHashCode()
{
var hashCode = -306707981;
hashCode = hashCode * -1521134295 + Id.GetHashCode();
hashCode = hashCode * -1521134295 + UpdatedAt.Ticks.GetHashCode();
return hashCode;
}
}
我的想法是,只要对象发生变化,就会有一个时间戳。 此时间戳与对象一起保存。 我也在考虑将此字段用作存储中的并发令牌。
由于 DateTime 的解析可能是一个问题,而不是使用时间,我认为 Guid 也是一个不错的选择,而不是 DateTime。 对象不会太多,所以 Guid 的唯一性应该不是问题。
您认为这种方法有问题吗?
就像我上面说的,我认为它比让 Equals 和 GetHashCode 遍历所有属性更容易实现和运行更快。
更新:越想越觉得在类上实现Equals 和GetHashCode 并不是一个好方法。我认为最好实现一个专门的IEqualityComparer<Person>,它以特定的方式比较Persons,并将其传递给LINQ的方法。
这样做的原因是,就像在 cmets 和 answer 中一样,Person 可以以不同的方式使用。
【问题讨论】:
-
GUID 或时间戳对您拥有的属性及其值一无所知。您已经拥有的
Id字段也没有。如果你能保证它们是独一无二的,那它们会比这更好吗? -
Person具有属性Id。这还不足以区分不同的实例吗?为什么该类的多个实例具有相同的Id? -- 如果您想跟踪实例数据的变化,那么还有其他方法可以做到这一点。例如使用property change。 -
另一方面,您可以拥有一个仅在实体更新时计算的私有哈希字段,因此您无需每次都重新计算。
-
IEquatable 分两步工作。 1)为每个项目创建一个哈希,并为哈希值创建一个二叉树。 2)然后当两个值具有相等的哈希值时,它使用 Equal 方法进行第二次比较。您可以让哈希对所有值都返回零,然后将 Equal 方法作为唯一的比较标准。您可以在返回 ID 时进行哈希处理,然后让 Equal 检查地址。
-
@DonBox - 所以你有一个类的两个实例(希望是相同的
Id),你想决定哪一个“更好”(更新)。 ——嗯,这不是那么容易,不是吗?想象一个只有Name和Surname属性的Person。在时间点 A,server 和 app 是相等的。在 B 点,应用程序(仅)更改了名称。在 C 点服务器更改(仅)姓氏。在 D 点,您要合并。服务器实例是否比应用程序实例“更好”?您想丢失任何一项更改吗?
标签: c# linq gethashcode iequatable