IEquatable<T> (usually in combination with overriding the inherited Object.Equals and Object.GetHashCode ) for all of your custom types. For composite types, call the Equals type method within the contained types. For contained collections, use the SequenceEqual extension method, which internally calls IEquatable<T>.Equals or Object.Equals for each element. This approach will obviously require you to extend the types of type definitions, but its results are faster than any general solutions involving serialization.
Edit : Here is a contrived example with three levels of nesting.
For value types, you can usually just call your Equals method. Even if fields or properties have never been explicitly assigned, they will still have a default value.
For reference types, you must first call ReferenceEquals , which checks for reference equality - this will serve as an increase in efficiency when you reference the same object. It will also handle cases where both links are null. If this NullReferenceException check fails, confirm that the field or property of your instance is not null (to avoid a NullReferenceException ) and call its Equals method. Since our members are correctly typed, the IEquatable<T>.Equals directly, bypassing the overridden Object.Equals method (which will be slightly slower due to the cast type).
When you override Object.Equals , you must also override Object.GetHashCode ; I did not do this below for the sake of brevity.
public class Person : IEquatable<Person> { public int Age { get; set; } public string FirstName { get; set; } public Address Address { get; set; } public override bool Equals(object obj) { return this.Equals(obj as Person); } public bool Equals(Person other) { if (other == null) return false; return this.Age.Equals(other.Age) && ( object.ReferenceEquals(this.FirstName, other.FirstName) || this.FirstName != null && this.FirstName.Equals(other.FirstName) ) && ( object.ReferenceEquals(this.Address, other.Address) || this.Address != null && this.Address.Equals(other.Address) ); } } public class Address : IEquatable<Address> { public int HouseNo { get; set; } public string Street { get; set; } public City City { get; set; } public override bool Equals(object obj) { return this.Equals(obj as Address); } public bool Equals(Address other) { if (other == null) return false; return this.HouseNo.Equals(other.HouseNo) && ( object.ReferenceEquals(this.Street, other.Street) || this.Street != null && this.Street.Equals(other.Street) ) && ( object.ReferenceEquals(this.City, other.City) || this.City != null && this.City.Equals(other.City) ); } } public class City : IEquatable<City> { public string Name { get; set; } public override bool Equals(object obj) { return this.Equals(obj as City); } public bool Equals(City other) { if (other == null) return false; return object.ReferenceEquals(this.Name, other.Name) || this.Name != null && this.Name.Equals(other.Name); } }
Update : This answer was written a few years ago. Since then, I began to move away from implementing IEquality<T> for mutable types for such scenarios. There are two concepts of equality: identity and equivalence . At the level of memory representation, they are widely distinguished as “referential equality” and “equal value” (see “ Comparison of Equalities” ). However, the same difference may also apply at the domain level. Suppose your Person class has a PersonId property that is unique to an individual person in the real world. Should two objects with the same PersonId values but different Age values be considered equal or different? The above answer assumes one after equivalence. However, there are many IEquality<T> interface, such as collections, which assume that such implementations provide identification. For example, if you TryGetValue(T,T) HashSet<T> , you usually expect TryGetValue(T,T) to return existing elements that share only the identity of your argument, not necessarily equivalent elements whose contents are exactly the same. This concept is respected by the notes in GetHashCode :
In general, for mutable link types, you should override GetHashCode() only if:
- You can calculate the hash code from fields that do not change; or
- You can make sure that the hash code of the object being modified does not change while the object is contained in a collection that relies on its hash code.