I have a tree of objects (DTOs), where one object references other objects and so on:
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 } These objects can be quite complex and have many other properties.
In my app, a Person with same Id could be in two storages, local storage in the app and coming from backend. I need to merge the online Person with local Person in a specific way, so for this I need to first know if the online Person is same with the one stored locally (in other words if local Person hasn't been updated by the app).
In order to use LINQ's Except, I know I need to implement Equatable<T> and the usual way I've seen it is like this:
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; } To me this sounds complicated and hard to maintain, it's easy to forget to update Equals and GetHashCode when properties change. Depending on the objects, it can also be a bit computational expensive.
Wouldn't the following be a simpler and much effective way of implementing Equals and 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; } } My idea is whenever the object changes, there's a timestamp. This timestamp gets saved along with the object. I am thinking to use this field as a concurrency token in storage too.
Since resolution of DateTime could be an issue, instead of using time, I'm thinking a Guid is also a good option instead of DateTime. There wouldn't be too many objects, so the uniqueness of Guid shouldn't be an issue.
Do you see a problem with this approach?
Like I said above, I think it would be much easier to implement and faster to run than having Equals and GetHashCode go over all the properties.
Update: The more I think about it, I tend to feel that having Equals and GetHashCode implemented on the class is not a good approach. I think it would be better to implement a specialized IEqualityComparer<Person> which compares Persons in a specific way and pass it to LINQ's methods.
The reason for this is because, like in the comments and answer, a Person could be used in different ways.
Idfield you already have. How would those be better than this - supposing you can guarantee them to be unique?Personhas the propertyId. Isn't that enough by itself to distinguish different instances? Why would you have more than one instance of that class with the sameId? -- if you want to track changes in the data of an instance, then there are other ways to do it. For example by using property change.Id) and you want to decide which one is "better" (more up-to-date). -- well, it ain't that easy, or is it? Imagine aPersonwith only the propertiesNameandSurname. At point A in time server and app are equal. At point B app changes (only) the name. At point C server changes (only) the surname. At point D, you want to merge. Is the server instance "better" than the app instance? Do you want to lose any one of the changes?