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\

18 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.

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!
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

Money market There is no need to be physically present for trading securities, as one can trade from anywhere through the internet or telephone

Merkur 15c Safety Razor - Barber Pole - Deccasino
Merkur casinosites.one 15C Safety Razor - casino-roll.com Merkur - https://deccasino.com/review/merit-casino/ 15C for https://jancasino.com/review/merit-casino/ Barber Pole is the 바카라 사이트 perfect introduction to the Merkur Safety Razor.

Canon IJ Network Tool is a toolkit software with the options to keep a check on most of your Canon printer network settings and adjust them according to your requirements.

Canon IJ Printer Utility is a complete software package meant to adjust and modify the configurations of your printer device as per the requirement. It is one of the essential utility software offered by Canon to ease your printer configuration alteration using a few clicks.


Canon.com/ijsetup/mg2500
is the Canon support link to get you the latest printer drivers and software to set up your PIXMA MG2500 or MG2520.

ij.start canon helps to set up canon printer. It’s the online support platform to download and install canon printer drivers, firmware, and software ij.start.canon , Canon IJ Network Tool

To keep current, you need to find trustworthy technology news sources that will provide you with up-to-date information.

blackboard san jac

inverse function calculator

qhs medical abbreviation

Post a Comment