Thursday, January 29, 2009

How to: Define Value Equality for a Type (C# Programming)

When you define a class or struct, you
decide whether it makes sense to create a custom definition of value
equality (or equivalence) for the type. Typically, you implement value
equality when objects of the type are expected to be added to a
collection of some sort, or when their primary purpose is to store a
set of fields or properties. You can base your definition of value
equality on a comparison of all the fields and properties in the type,
or you can base the definition on a subset. But in either case, and in
both classes and structs, your implementation should follow the five
guarantees of equivalence:


  1. x.Equals(x) returns true. This is called the reflexive property.


  2. x.Equals(y) returns the same value as y.Equals(x). This is called the symmetric property.


  3. if (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true. This is called the transitive property.


  4. Successive invocations of x.Equals(y) return the same value as long as the objects referenced by x and y are not modified.


  5. x.Equals(null) returns false. However, null.Equals(null) throws an exception; it does not obey rule number two above.


Any struct that you define already has a default implementation of value equality that it inherits from the System..::.ValueType override of the Object..::.Equals(Object)
method. This implementation uses reflection to examine all the public
and non-public fields and properties in the type. Although this
implementation produces correct results, it is relatively slow compared
to a custom implementation that you write specifically for the type.

The implementation details for value equality
are different for classes and structs. However, both classes and
structs require the same basic steps for implementing equality:


  1. Override the virtual Object..::.Equals(Object) method. In most cases, your implementation of bool Equals( object obj ) should just call into the type-specific Equals method that is the implementation of the System..::.IEquatable<(Of <(T>)>) interface. (See step 2.)


  2. Implement the System..::.IEquatable<(Of <(T>)>) interface by providing a type-specific Equals
    method. This is where the actual equivalence comparison is performed.
    For example, you might decide to define equality by comparing only one
    or two fields in your type. Do not throw exceptions from Equals. For classes only: This method should examine only fields that are declared in the class. It should call base.Equals to examine fields that are in the base class. (Do not do this if the type inherits directly from Object, because the Object implementation of Object..::.Equals(Object) performs a reference equality check.)


  3. Optional but recommended: Overload the == and != operators.


  4. Override Object..::.GetHashCode so that two objects that have value equality produce the same hash code.


  5. Optional: To support definitions for "greater than" or "less than," implement the IComparable<(Of <(T>)>) interface for your type, and also overload the <= and >= operators.


The first example that follows shows a class implementation. The second
example shows a struct implementation.




The following example shows how to implement value equality in a class (reference type).

<span style="color: blue;">namespace</span> ValueEquality<br />{<br />    <span style="color: blue;">using</span> System;<br />    <span style="color: blue;">class</span> TwoDPoint : IEquatable<TwoDPoint><br />    {<br />        <span style="color: green;">// Readonly auto-implemented properties.</span><br />        <span style="color: blue;">public</span> <span style="color: blue;">int</span> X { <span style="color: blue;">get</span>; <span style="color: blue;">private</span> <span style="color: blue;">set</span>; }<br />        <span style="color: blue;">public</span> <span style="color: blue;">int</span> Y { <span style="color: blue;">get</span>; <span style="color: blue;">private</span> <span style="color: blue;">set</span>; }<br /><br />        <span style="color: green;">// Set the properties in the constructor.</span><br />        <span style="color: blue;">public</span> TwoDPoint(<span style="color: blue;">int</span> x, <span style="color: blue;">int</span> y)<br />        {<br />            <span style="color: blue;">if</span> ((x < 1) || (x > 2000) || (y < 1) || (y > 2000))<br />                throw <span style="color: blue;">new</span> System.ArgumentException(<span style="color: maroon;"><span style="color: maroon;">"Point must be in range 1 - 2000"</span></span>);<br />            <span style="color: blue;">this</span>.X = x;<br />            <span style="color: blue;">this</span>.Y = y;<br />        }<br /><br />        <span style="color: blue;">public</span> override <span style="color: blue;">bool</span> Equals(object obj)<br />        {<br />            <span style="color: blue;">return</span> <span style="color: blue;">this</span>.Equals(obj as TwoDPoint);<br />        }<br /><br />        <span style="color: blue;">public</span> <span style="color: blue;">bool</span> Equals(TwoDPoint p)<br />        {<br />            <span style="color: green;">// If parameter is null, return false.</span><br />            <span style="color: blue;">if</span> (Object.ReferenceEquals(p, <span style="color: blue;">null</span>))<br />            {<br />                <span style="color: blue;">return</span> <span style="color: blue;">false</span>;<br />            }<br /><br />            <span style="color: green;">// Optimization for a common success case.</span><br />            <span style="color: blue;">if</span> (Object.ReferenceEquals(<span style="color: blue;">this</span>, p))<br />            {<br />                <span style="color: blue;">return</span> <span style="color: blue;">true</span>;<br />            }<br /><br />            <span style="color: green;">// If run-time types are not exactly the same, return false.</span><br />            <span style="color: blue;">if</span> (<span style="color: blue;">this</span>.GetType() != p.GetType())<br />                <span style="color: blue;">return</span> <span style="color: blue;">false</span>;<br /><br />            <span style="color: green;">// Return true if the fields match.</span><br />            <span style="color: green;">// Note that the base class is not invoked because it is</span><br />            <span style="color: green;">// System.Object, which defines Equals as reference equality.</span><br />            <span style="color: blue;">return</span> (X == p.X) && (Y == p.Y);<br />        }<br /><br />        <span style="color: blue;">public</span> override <span style="color: blue;">int</span> GetHashCode()<br />        {<br />            <span style="color: blue;">return</span> X * 0x00010000 + Y;<br />        }<br /><br />        <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">bool</span> operator ==(TwoDPoint lhs, TwoDPoint rhs)<br />        {<br />            <span style="color: green;">// Check for null on left side.</span><br />            <span style="color: blue;">if</span> (Object.ReferenceEquals(lhs, <span style="color: blue;">null</span>))<br />            {<br />                <span style="color: blue;">if</span> (Object.ReferenceEquals(rhs, <span style="color: blue;">null</span>))<br />                {<br />                    <span style="color: green;">// null == null = true.</span><br />                    <span style="color: blue;">return</span> <span style="color: blue;">true</span>;<br />                }<br /><br />                <span style="color: green;">// Only the left side is null.</span><br />                <span style="color: blue;">return</span> <span style="color: blue;">false</span>;<br />            }<br />            <span style="color: green;">// Equals handles case of null on right side.</span><br />            <span style="color: blue;">return</span> lhs.Equals(rhs);<br />        }<br /><br />        <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">bool</span> operator !=(TwoDPoint lhs, TwoDPoint rhs)<br />        {<br />            <span style="color: blue;">return</span> !(lhs == rhs);<br />        }<br />    }<br /><br />    <span style="color: green;">// For the sake of simplicity, assume a ThreeDPoint IS a TwoDPoint.</span><br />    <span style="color: blue;">class</span> ThreeDPoint : TwoDPoint, IEquatable<ThreeDPoint><br />    {<br />        <span style="color: blue;">public</span> <span style="color: blue;">int</span> Z { <span style="color: blue;">get</span>; <span style="color: blue;">private</span> <span style="color: blue;">set</span>; }<br /><br />        <span style="color: blue;">public</span> ThreeDPoint(<span style="color: blue;">int</span> x, <span style="color: blue;">int</span> y, <span style="color: blue;">int</span> z)<br />            : <span style="color: blue;">base</span>(x, y)<br />        {<br />            <span style="color: blue;">if</span> ((z < 1) || (z > 2000))<br />                throw <span style="color: blue;">new</span> System.ArgumentException(<span style="color: maroon;"><span style="color: maroon;">"Point must be in range 1 - 2000"</span></span>);<br />            <span style="color: blue;">this</span>.Z = z;<br />        }<br /><br />        <span style="color: blue;">public</span> override <span style="color: blue;">bool</span> Equals(object obj)<br />        {<br />            <span style="color: blue;">return</span> <span style="color: blue;">this</span>.Equals(obj as ThreeDPoint);<br />        }<br /><br />        <span style="color: blue;">public</span> <span style="color: blue;">bool</span> Equals(ThreeDPoint p)<br />        {<br />            <span style="color: green;">// If parameter is null, return false.</span><br />            <span style="color: blue;">if</span> (Object.ReferenceEquals(p, <span style="color: blue;">null</span>))<br />            {<br />                <span style="color: blue;">return</span> <span style="color: blue;">false</span>;<br />            }<br /><br />            <span style="color: green;">// Optimization for a common success case.</span><br />            <span style="color: blue;">if</span>(Object.ReferenceEquals(<span style="color: blue;">this</span>, p))<br />            {<br />                <span style="color: blue;">return</span> <span style="color: blue;">true</span>;<br />            }<br /><br />            <span style="color: green;">// Check properties that this class declares.</span><br />            <span style="color: blue;">if</span> (Z == p.Z)<br />            {<br />                <span style="color: green;">// Let base class check its own fields </span><br />                <span style="color: green;">// and do the run-time type comparison.</span><br />                <span style="color: blue;">return</span> <span style="color: blue;">base</span>.Equals((TwoDPoint)p);<br />            }<br />            <span style="color: blue;">else</span><br />                <span style="color: blue;">return</span> <span style="color: blue;">false</span>;<br />        }<br /><br />        <span style="color: blue;">public</span> override <span style="color: blue;">int</span> GetHashCode()<br />        {<br />            <span style="color: blue;">return</span> (X * 0x100000) + (Y * 0x1000) + Z;<br />        }<br /><br />        <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">bool</span> operator ==(ThreeDPoint lhs, ThreeDPoint rhs)<br />        {<br />            <span style="color: green;">// Check for null.</span><br />            <span style="color: blue;">if</span> (Object.ReferenceEquals(lhs, <span style="color: blue;">null</span>))<br />            {<br />                <span style="color: blue;">if</span> (Object.ReferenceEquals(lhs, <span style="color: blue;">null</span>))<br />                {<br />                    <span style="color: green;">// null == null = true.</span><br />                    <span style="color: blue;">return</span> <span style="color: blue;">true</span>;<br />                }<br /><br />                <span style="color: green;">// Only the left side is null.</span><br />                <span style="color: blue;">return</span> <span style="color: blue;">false</span>;<br />            }<br />            <span style="color: green;">// Equals handles the case of null on right side.</span><br />            <span style="color: blue;">return</span> lhs.Equals(rhs);<br />        }<br /><br />        <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">bool</span> operator !=(ThreeDPoint lhs, ThreeDPoint rhs)<br />        {<br />            <span style="color: blue;">return</span> !(lhs == rhs);<br />        }<br />    }<br /><br />    <span style="color: blue;">class</span> Program<br />    {<br />        <span style="color: blue;">static</span> <span style="color: blue;">void</span> Main(<span style="color: blue;">string</span>[] args)<br />        {<br />            ThreeDPoint pointA = <span style="color: blue;">new</span> ThreeDPoint(3, 4, 5);<br />            ThreeDPoint pointB = <span style="color: blue;">new</span> ThreeDPoint(3, 4, 5);<br />            ThreeDPoint pointC = <span style="color: blue;">null</span>;<br />            <span style="color: blue;">int</span> i = 5;<br /><br />            Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"pointA.Equals(pointB) = {0}"</span></span>, pointA.Equals(pointB));<br />            Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"pointA == pointB = {0}"</span></span>, pointA == pointB);<br />            Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"null comparison = {0}"</span></span>, pointA.Equals(pointC));<br />            Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"Compare to some other type = {0}"</span></span>, pointA.Equals(i));<br /><br />            TwoDPoint pointD = <span style="color: blue;">null</span>;<br />            TwoDPoint pointE = <span style="color: blue;">null</span>;<br /><br /><br /><br />            Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"Two null TwoDPoints are equal: {0}"</span></span>, pointD == pointE);<br /><br />            pointE = <span style="color: blue;">new</span> TwoDPoint(3, 4);<br />            Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"(pointE == pointA) = {0}"</span></span>, pointE == pointA);<br />            Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"(pointA == pointE) = {0}"</span></span>, pointA == pointE);<br />            Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"(pointA != pointE) = {0}"</span></span>, pointA != pointE);<br /><br />            System.Collections.ArrayList list = <span style="color: blue;">new</span> System.Collections.ArrayList();<br />            list.Add(<span style="color: blue;">new</span> ThreeDPoint(3, 4, 5));<br />            Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"pointE.Equals(list[0]): {0}"</span></span>, pointE.Equals(list[0]));<br /><br />            <span style="color: green;">// Keep the console window open in debug mode.</span><br />            System.Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"Press any key to exit."</span></span>);<br />            System.Console.ReadKey();<br />        }<br />    }<br /><br />    <span style="color: green;">/* Output:<br />        pointA.Equals(pointB) = True<br />        pointA == pointB = True<br />        <span style="color: blue;">null</span> comparison = False<br />        Compare to some other type = False<br />        Two <span style="color: blue;">null</span> TwoDPoints are equal: True<br />        (pointE == pointA) = False<br />        (pointA == pointE) = False<br />        (pointA != pointE) = True<br />        pointE.Equals(list[0]): False<br />    */</span><br />}<br /><br /><br /></pre></div></div></div><p>
On classes (reference types), the default implementation of both <span><a id="ctl00_rs1_mainContentContainer_ctl42" onclick="javascript:Track('ctl00_rs1_mainContentContainer_cpe198786_c|ctl00_rs1_mainContentContainer_ctl42',this);" href="http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx">Object<span class="cs">.</span><span class="vb">.</span><span class="cpp">::</span><span class="nu">.</span>Equals(Object)</a></span>
methods performs a reference equality comparison, not a value equality
check. When an implementer overrides the virtual method, the purpose is
to give it value equality semantics. </p><p>
The <span><span class="input">==</span></span> and <span><span class="input">!=</span></span>
operators can be used with classes even if the class does not overload
them. However, the default behavior is to perform a reference equality
check. In a class, if you overload the <span><span class="input">Equals</span></span> method, you should overload the <span><span class="input">==</span></span> and <span><span class="input">!=</span></span> operators, but it is not required.
</p><p>
The following example shows how to implement value equality in a struct (value type):
</p><div id="snippetGroup1"><div class="libCScode" id="ctl00_rs1_mainContentContainer_ctl44_CSharp"><div class="CodeSnippetTitleBar"><div class="CodeDisplayLanguage">C#</div><div class="CopyCodeButton"><a class="copyCode" title="Copy Code" href="javascript:CopyCode('ctl00_rs1_mainContentContainer_ctl44CSharp');"><img class="LibC_copy_off" src="http://i.msdn.microsoft.com/Global/Images/clear.gif" align="middle" border="0" height="9" /> Copy Code</a></div></div><div dir="ltr"><pre class="libCScode" style="white-space: pre-wrap;" id="ctl00_rs1_mainContentContainer_ctl44CSharp" space="preserve"> struct TwoDPoint : IEquatable<TwoDPoint><br /> {<br /> <span style="color: green;">// Read/write auto-implemented properties.</span><br /> <span style="color: blue;">public</span> <span style="color: blue;">int</span> X { <span style="color: blue;">get</span>; <span style="color: blue;">private</span> <span style="color: blue;">set</span>; }<br /> <span style="color: blue;">public</span> <span style="color: blue;">int</span> Y { <span style="color: blue;">get</span>; <span style="color: blue;">private</span> <span style="color: blue;">set</span>; }<br /><br /> <span style="color: blue;">public</span> TwoDPoint(<span style="color: blue;">int</span> x, <span style="color: blue;">int</span> y) : <span style="color: blue;">this</span>()<br /> {<br /> X = x;<br /> Y = x;<br /> }<br /><br /> <span style="color: blue;">public</span> override <span style="color: blue;">bool</span> Equals(object obj)<br /> {<br /> <span style="color: blue;">if</span> (obj is TwoDPoint)<br /> {<br /> <span style="color: blue;">return</span> <span style="color: blue;">this</span>.Equals((TwoDPoint)obj);<br /> }<br /> <span style="color: blue;">return</span> <span style="color: blue;">false</span>;<br /> }<br /><br /> <span style="color: blue;">public</span> <span style="color: blue;">bool</span> Equals(TwoDPoint p)<br /> {<br /> <span style="color: blue;">return</span> (X == p.X) && (Y == p.Y);<br /> }<br /><br /> <span style="color: blue;">public</span> override <span style="color: blue;">int</span> GetHashCode()<br /> {<br /> <span style="color: blue;">return</span> X ^ Y;<br /> }<br /><br /> <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">bool</span> operator ==(TwoDPoint lhs, TwoDPoint rhs)<br /> {<br /> <span style="color: blue;">return</span> lhs.Equals(rhs);<br /> }<br /><br /> <span style="color: blue;">public</span> <span style="color: blue;">static</span> <span style="color: blue;">bool</span> operator !=(TwoDPoint lhs, TwoDPoint rhs)<br /> {<br /> <span style="color: blue;">return</span> !(lhs.Equals(rhs));<br /> }<br /> }<br /><br /><br /> <span style="color: blue;">class</span> Program<br /> {<br /> <span style="color: blue;">static</span> <span style="color: blue;">void</span> Main(<span style="color: blue;">string</span>[] args)<br /> {<br /> TwoDPoint pointA = <span style="color: blue;">new</span> TwoDPoint(3,4);<br /> TwoDPoint pointB = <span style="color: blue;">new</span> TwoDPoint(3,4);<br /> <span style="color: blue;">int</span> i = 5;<br /><br /> <span style="color: green;">// Compare using virtual Equals, static Equals, and == and != operators.</span><br /> <span style="color: green;">// True:</span><br /> Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"pointA.Equals(pointB) = {0}"</span></span>, pointA.Equals(pointB));<br /> <span style="color: green;">// True:</span><br /> Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"pointA == pointB = {0}"</span></span>, pointA == pointB);<br /> <span style="color: green;">// True:</span><br /> Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"Object.Equals(pointA, pointB) = {0}"</span></span>, Object.Equals(pointA, pointB)); <br /> <span style="color: green;">// False:</span><br /> Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"pointA.Equals(null) = {0}"</span></span>, pointA.Equals(<span style="color: blue;">null</span>));<br /> <span style="color: green;">// False:</span><br /> Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"(pointA == null) = {0}"</span></span>, pointA == <span style="color: blue;">null</span>);<br /> <span style="color: green;">// True:</span><br /> Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"(pointA != null) = {0}"</span></span>, pointA != <span style="color: blue;">null</span>);<br /> <span style="color: green;">// False:</span><br /> Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"pointA.Equals(i) = {0}"</span></span>, pointA.Equals(i)); <br /> <span style="color: green;">// CS0019:</span><br /> <span style="color: green;">// Console.WriteLine("pointA == i = {0}", pointA == i); </span><br /><br /> <span style="color: green;">// Compare unboxed to boxed.</span><br /> System.Collections.ArrayList list = <span style="color: blue;">new</span> System.Collections.ArrayList();<br /> list.Add(<span style="color: blue;">new</span> TwoDPoint(3,4));<br /> <span style="color: green;">// True:</span><br /> Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"pointE.Equals(list[0]): {0}"</span></span>, pointA.Equals(list[0])); <br /><br /><br /> <span style="color: green;">// Compare nullable to nullable and to non-nullable.</span><br /> TwoDPoint? pointC = <span style="color: blue;">null</span>;<br /> TwoDPoint? pointD = <span style="color: blue;">null</span>;<br /> <span style="color: green;">// False:</span><br /> Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"pointA == (pointC = null) = {0}"</span></span>, pointA == pointC);<br /> <span style="color: green;">// True:</span><br /> Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"pointC == pointD = {0}"</span></span>, pointC == pointD); <br /><br /> TwoDPoint temp = <span style="color: blue;">new</span> TwoDPoint(3,4);<br /> pointC = temp;<br /> <span style="color: green;">// True:</span><br /> Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"pointA == (pointC = 3,4) = {0}"</span></span>, pointA == pointC); <br /><br /> pointD = temp;<br /> <span style="color: green;">// True:</span><br /> Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"pointD == (pointC = 3,4) = {0}"</span></span>, pointD == pointC); <br /><br /> <span style="color: green;">// Keep the console window open in debug mode.</span><br /> System.Console.WriteLine(<span style="color: maroon;"><span style="color: maroon;">"Press any key to exit."</span></span>);<br /> System.Console.ReadKey();<br /> }<br /> }<br /><br /> <span style="color: green;">/* Output:<br /> pointA.Equals(pointB) = True<br /> pointA == pointB = True<br /> Object.Equals(pointA, pointB) = True<br /> pointA.Equals(<span style="color: blue;">null</span>) = False<br /> (pointA == <span style="color: blue;">null</span>) = False<br /> (pointA != <span style="color: blue;">null</span>) = True<br /> pointA.Equals(i) = False<br /> pointE.Equals(list[0]): True<br /> pointA == (pointC = <span style="color: blue;">null</span>) = False<br /> pointC == pointD = True<br /> pointA == (pointC = 3,4) = True<br /> pointD == (pointC = 3,4) = True<br /> */</span><br />}<br /><br />


For structs, the default implementation of Object..::.Equals(Object) (which is the overridden version in System..::.ValueType)
performs a value equality check by using reflection to compare the
values of every field in the type. When an implementer overrides the
virtual Equals method in a
stuct, the purpose is to provide a more efficient means of performing
the value equality check and optionally to base the comparison on some
subset of the struct's field or properties.


The == and != operators cannot operate on a struct unless the struct explicitly overloads them.



blog comments powered by Disqus