SLaks.Blog

Making the world a better place, one line of code at a time

Simplifying Value Comparison Semantics

Posted on Wednesday, December 29, 2010, at 3:10:00 AM UTC

A common chore in developing real-world C# applications is implementing value semantics for equality.  This involves implementing IEquatable<T>, overriding Equals() and GetHashCode(), and overloading the == and != operators.

Implementing these methods is a time-consuming and repetitive task, and is easy to get wrong, especially GetHashCode().  In particular, the best way implement GetHashCode() is much more complicated than return x.GetHashCode() ^ y.GetHashCode().

To simplify this task, I created a ValueComparer class:

///<summary>
/// Contains all of the properties of a class that
/// are used to provide value semantics.
///</summary>
///<remarks>
/// You can create a static readonly ValueComparer for your class,
/// then call into it from Equals, GetHashCode, and CompareTo.
///</remarks>
class ValueComparer<T> : IComparer<T>, IEqualityComparer<T> {
public ValueComparer(params Func<T, object>[] props) {
Properties = new ReadOnlyCollection<Func<T, object>>(props);
}

public ReadOnlyCollection<Func<T, object>> Properties
{ get; private set; }

public bool Equals(T x, T y) {
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
//Object.Equals handles strings and nulls correctly
return Properties.All(f => Equals(f(x), f(y)));
}

//http://stackoverflow.com/questions/263400/263416#263416
public int GetHashCode(T obj) {
if (obj == null) return -42;
unchecked {
int hash = 17;
foreach (var prop in Properties) {
object value = prop(obj);
if (value == null)
hash = hash * 23 - 1;
else
hash = hash * 23 + value.GetHashCode();
}
return hash;
}
}

public int Compare(T x, T y) {
foreach (var prop in Properties) {
//The properties can be any type including null.
var comp = Comparer.DefaultInvariant
.Compare(prop(x), prop(y));
if (comp != 0)
return comp;
}
return 0;
}
}

This class implements an external comparer that compares two instances by an ordered list of properties.

ValueComparer can be used as a standalone IComparer<T> or IEqualityComparer<T> implementation.

It can also be used to implement value semantics within a type.
For example:

class Person : IComparable<Person>, IEquatable<Person>, IComparable {
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
public string Email { get; set; }

public override int GetHashCode() { return Comparer.GetHashCode(this); }
public int CompareTo(Person obj) { return Comparer.Compare(this, obj); }
int IComparable.CompareTo(object obj) { return CompareTo(obj as Person); }
public bool Equals(Person obj) { return Comparer.Equals(this, obj); }
public override bool Equals(object obj) { return Equals(obj as Person); }
static readonly ValueComparer<Person> Comparer = new ValueComparer<Person>(
o => o.LastName,
o => o.FirstName,
o => o.Address,
o => o.Phone,
o => o.Email
);
}

To simplify this task, I created a code snippet:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>ValueComparer</Title>
<Shortcut>vc</Shortcut>
<Description>Code snippet for equality methods using ValueComparer</Description>
<Author>SLaks</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal Editable="false">
<ID>classname</ID>
<ToolTip>Class name</ToolTip>
<Default>ClassNamePlaceholder</Default>
<Function>ClassName()</Function>
</Literal>
</Declarations>
<Code Language="csharp">
<![CDATA[public override int GetHashCode() { return Comparer.GetHashCode(this); }
public int CompareTo($classname$ obj) { return Comparer.Compare(this, obj); }
int IComparable.CompareTo(object obj) { return CompareTo(obj as $classname$); }
public bool Equals($classname$ obj) { return Comparer.Equals(this, obj); }
public override bool Equals(object obj) { return Equals(obj as $classname$); }
static readonly ValueComparer<$classname$>
Comparer = new ValueComparer<$classname$>(
o => o.$end$
);]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
It can also be downloaded here; save it to My Documents\Visual Studio 2010\Code Snippets\Visual C#\My Code Snippets\

Categories: comparison, C#, .Net Tweet this post

comments powered by Disqus