As you've said, you only need to implement the correct IEquatable<MyObject>
interface in your MyObject
, or implement an IEqualityComparer<MyObject>
/// <summary>
/// Fully equatable MyObject
/// </summary>
public class MyObject : IEquatable<MyObject>
{
public int Id { get; set; }
public string Name { get; set; }
public override bool Equals(object obj)
{
// obj is object, so we can use its == operator
if (obj == null)
{
return false;
}
MyObject other = obj as MyObject;
if (object.ReferenceEquals(other, null))
{
return false;
}
return this.InnerEquals(other);
}
public bool Equals(MyObject other)
{
if (object.ReferenceEquals(other, null))
{
return false;
}
return this.InnerEquals(other);
}
private bool InnerEquals(MyObject other)
{
// Here we know that other != null;
if (object.ReferenceEquals(this, other))
{
return true;
}
return this.Id == other.Id && this.Name == other.Name;
}
public override int GetHashCode()
{
unchecked
{
// From http://stackoverflow.com/a/263416/613130
int hash = 17;
hash = hash * 23 + this.Id.GetHashCode();
hash = hash * 23 + (this.Name != null ? this.Name.GetHashCode() : 0);
return hash;
}
}
}
and then you can use
if (!oldList.SequenceEqual(newList))
Note that this will compare element order! If you change element order, then the comparison will return false
Or you can use an "external" IEqualityComparer<MyObject>
public class MyObjectEqualityComparer : IEqualityComparer<MyObject>
{
public static readonly MyObjectEqualityComparer Default = new MyObjectEqualityComparer();
protected MyObjectEqualityComparer()
{
}
public bool Equals(MyObject x, MyObject y)
{
if (object.ReferenceEquals(x, null))
{
return object.ReferenceEquals(y, null);
}
if (object.ReferenceEquals(y, null))
{
return false;
}
// Here we know that x != null && y != null;
if (object.ReferenceEquals(x, y))
{
return true;
}
return x.Id == y.Id && x.Name == y.Name;
}
public int GetHashCode(MyObject obj)
{
if (obj == null)
{
return 0;
}
unchecked
{
// From http://stackoverflow.com/a/263416/613130
int hash = 17;
hash = hash * 23 + obj.Id.GetHashCode();
hash = hash * 23 + (obj.Name != null ? obj.Name.GetHashCode() : 0);
return hash;
}
}
}
use it like
if (!oldList.SequenceEqual(newList, MyObjectEqualityComparer.Default))
Note that there are various schools of thought on how exactly to write equality comparers, and there is a little caveat: if you override the operator==
you must be very very wary of not using it inside your comparator :-)
A classical example of wrong code, when the operator==
is overloaded
public bool Equals(MyObject other)
{
// BOOOM!!! StackOverflowException!
// Equals will call operator== that will probably call
// Equals back! and so on and so on.
if (other == null)
{
return false;
}
return this.InnerEquals(other);
}
So it is better to do object.ReferenceEquals(something, someotherthing)
that does reference comparison.
Then there is the problem of null
handling with properties:
The hash of Name
(a string
) is written like this:
hash = hash * 23 + obj.Name != null ? obj.Name.GetHashCode() : 0
so that if obj.Name
is null
the code doesn't expode in a NullReferenceException
. Automatically generated code for anonymous objects use another way:
hash = hash * 23 + EqualityComparer<string>.Default.GetHashCode(obj.Name);
The EqualityComparer<string>.Default
is safe to use, even with null
values.
For the Equals
comparison of properties, the automatically generated code for anonymous objects uses another funny trick:
&& EqualityComparer<string>.Default.Equals(this.Name, obj.Name);
so it uses EqualityComparer<string>.Default.Equals
, that then correctly uses the various methods/interfaces to compare objects.