Simplifying Value Comparison Semantics

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\

19 comments:

Pasting the ValueComparer class into VS 2010 C# gives me 6 errors.


The type or namespace name 'ReadOnlyCollection' could not be found (are you missing a using directive or an assembly

The type or namespace name 'ReadOnlyCollection' could not be found (are you missing a using directive or an assembly reference?)

Operator '==' cannot be applied to operands of type 'T' and 'T'

foreach statement cannot operate on variables of type 'ReadOnlyCollection>' because 'ReadOnlyCollection>' does not contain a public definition for 'GetEnumerator'

foreach statement cannot operate on variables of type 'ReadOnlyCollection>' because 'ReadOnlyCollection>' does not contain a public definition for 'GetEnumerator'

Using the generic type 'System.Collections.Generic.Comparer' requires 1 type arguments


Adding "using System.Collections.ObjectModel;" got rid of 4 of the errors, related to ReadOnlyCollection not being recognized, leaving me with these 2 errors:

Operator '==' cannot be applied to operands of type 'T' and 'T'

Using the generic type 'System.Collections.Generic.Comparer' requires 1 type arguments

Adding "using System.Collections;" fixes 1 of the remaining two errors. Leaving me with just:

Operator '==' cannot be applied to operands of type 'T' and 'T'

You're right; that's my fault.
I needed to call ReferenceEquals.

I fixed the code in the original post.

Thanks for catching my mistake!

Hi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me.. I am a regular follower of your blog. Really very informative post you shared here. Kindly keep blogging. If anyone wants to become a .Net developer learn from Dot Net Training in Chennai. or learn thru Dot Net Training in Chennai. Nowadays Dot Net has tons of job opportunities on various vertical industry.
or Javascript Training in Chennai. Nowadays JavaScript has tons of job opportunities on various vertical industry.

I would like to thank you for the great text. Karina

In my opinion, it's very important to read https://samedaypaper.org/blog/narrative-essay-topics if you are looking for narrative essay writing advices. It was helpful for me and my friends when we were students.

Have questions to enter the online casino? Come to our section of rock BGAOC. best real online gambling slots Come and do not get lost. win with us.

Online biology essay services have come up with Biology Essay Writing Services for biology research paper writing service students in order for them to score straight A’s in their custom biology research paper services.

To seek the best Life Science Writing Services for those studying biology coursework writing services, it is important to hire an award winning science paper writing service company.

Amazing post, audience will be engaged at best with your blog, we are leading as AWS live class software development company willing to contribute to the educational sector with high end secured software for live classes.

Thanks for this important information through your website. QuickBooks Firewall Error acts as a barrier between the quickbooks company file and quickbooks desktop. It doesn't allow the quickbooks software to communicate with the quickbooks company file and this error has been experienced by the users many times.quickbooks database server manager windows firewall is blocking quickbooks

I look forward to fresh updates and will share this website with my Facebook group!
McAfee programming gives the digital security services to ensure your hardware information.Install mcafee from www.mcafee.com/activate |
How to use McAfee Removal tool ?
What is McAfee total protection Error while installing updates on Mac ?
What is McAfee LiveSafe Connect VPN and its benefits?
How to Uninstall Avast in Windows 10

How McAfee Total Protection helps you to be secure on the internet?
What is McAfee LiveSafe Security.
How to Register McAfee on Lenovo ?
How to Register McAfee on dell ?

It's really a nice and useful piece of info.
Setup your ms office from office.com/setup |
What is the use of Windows 10 media creation tool
how to find How to find windows 10 product key
Do you know you can Install office without account .
How to Create Windows 10 recovery USB

I look forward to fresh updates and will share this website with my Facebook group!
Find your HP printer support
Easy way for How to connect hp printer to wifi
Why is my HP printer offline
Fix your printer by printer troubleshooting
Why is my printer connected but not printing

Post a Comment