C # Comparison of equality

Source: Internet
Author: User

This article describes the comparison of Equality in C #, which focuses on the following two areas

= = and! = operators, when they can be used for equality comparisons, when they are not applicable, and if not, what are their alternatives?

When, you need to customize the equality comparison logic for a type

Before we explain equality comparisons and how to customize equality comparison logic, let's first look at value type comparisons and reference type comparisons

Value type comparison vs. reference type comparison

There are two kinds of equality comparisons in C #:

    • Value types are equal, and two values are equal in some scenario
    • Reference types are equal, two references point to the same object

By default,

    • Value types are equal with value types
    • Reference types use reference equality

In fact, value types can only use value equality (unless the value type is boxed). See a simple example (compare two numbers) and run the result to True

int x = 5, y = 5; Console.WriteLine (x = = y);

By default, reference types use reference equality. such as the following example: return False

Object x = 5, y = 5; Console.WriteLine (x = = y);

If x and Y point to the same object, then true is returned:

Object x = 5, y = x; Console.WriteLine (x = = y);
Criteria for equality

The following three criteria are used to achieve equality comparisons:

    • = = and! = Operators
    • Virtual method in Object equals
    • Iequatable<t> interface

Let's take a separate description

1. = = and! = Operators

The reason for using = = and! = is that they are operators that implement equality comparisons through static functions. Therefore, when you use = = or! =, C # Determines the type of comparison at compile time and does not execute any virtual methods (Object.Equals). This is the equivalent line comparison that everyone expects. In the first example of a numeric comparison, for example, the compiler determines at compile time that the type of the Execute = = operation is of type int, because both x and y are of type int.

In the second example, the compiler determines that the type of the Execute = = operation is type object because object is a class (reference type), so the = = operator of the object takes reference equality to compare X and Y. The result returns false, because X and Y point to different objects on the heap (the boxed int)

2. Object.Equals virtual method

To correctly compare the X and Y in the second example, we can use the equals virtual method. The equals virtual method is defined in System.Object, which applies to all types

Object x = 5, y = 5; Console.WriteLine (X.equals (y));

equals determines the type of comparison when the program is run --compared by the actual type of the object. In the above example, the Euqals method of Int32 is called, and the method uses the value equality to compare, so the above example returns True. If x and Y are reference types, then the call reference equality is compared, and if x and y are struct types, then equals invokes the Equals method of each member of the struct to compare the corresponding type.

As you can see here, you might wonder why C # designers do not design = = as Virtaul, so that it is like equals to avoid an appeal flaw. This is because:

    • If the first operand is a null,equals method, the NullReferenceException exception is thrown, and the static operator does not
    • Because the = = operator determines the comparison type (statically resolved comparison type) at compile time, it executes very quickly. This also makes it not too much of a performance impact to write a large number of arithmetic code to perform equality comparisons
    • Sometimes, = = and equals are applied to different scenarios for equality comparisons. (subsequent content will be covered)

In short, complex designs reflect complex scenarios: the concept of equality involves many scenarios.

While the Euqals method is suitable for comparing objects of two unknown types, the following method is suitable for comparing two objects of any type:

public static bool AreEqual (object Obj1, Object obj2) {    return obj1. Equals (OBJ2);}

However, the function cannot handle the case where the first argument is null, and if the first function is NULL, you will get an NullReferenceException exception. So we need to modify the function:

public static bool AreEqual (object Obj1, Object obj2) {    if (obj1 = = null)        return obj2 = null;    Return OBJ1. Equals (OBJ2);}

Static Equals method for Object

The object class also defines a static Equals method, which acts the same as the Areequals method.

public static bool Equals (object Obja, Object objb) {    if (OBJA==OBJB) {        return true;    }    if (Obja==null | | objb==null) {        return false;    }    Return Obja.equals (OBJB);}

This allows for a safe comparison of null objects at compile time that do not know the type.

Object x = 5, y = 5; Console.WriteLine (object. Equals (x, y)); -Truex = null; Console.WriteLine (object. Equals (x, y)); -Falsey = null; Console.WriteLine (object. Equals (x, y)); Trueconsole.writeline (X.equals (y)); -Nullreferebceexception, because x is null

Note that when you write the generic type, the following code will not compile (unless you replace the = = or! = Operator with the call to the Object.Equals method):

public class test<t>: iequalitycomparer<t>{    T _value;    public void SetValue (T newvalue)    {        //Operator '! = ' cannot be applied to operands of type ' t ' and ' t '/        /IT Should Be:if (!object. Equals (NewValue, _value))        if (newvalue! = _value)            _value = newvalue;    }}

Static ReferenceEquals method of Object

Sometimes you need to force a comparison of two references for equality. At this point, you need to use object. ReferenceEquals:

internal class Widget {public string UID {get; set;}        public override bool Equals (object obj) {if (obj = = null) return this = = NULL; if (! (        obj is Widget) return false;        Widget w = obj as Widget; return this.    UID = = W.uid; } public override int GetHashCode () {return this. Uid.            GetHashCode (); } public static bool operator = = (widget w1, widget W2) {return W1.            Equals (W2); public static bool Operator! = (widget w1, widget W2) {return!W1.            Equals (W2);    }}static void Main (string[] args) {widget w1 = new Widget ();    Widget W2 = new Widget (); Console.WriteLine (W1==W2); -True Console.WriteLine (W1. Equals (W2)); -True Console.WriteLine (object. ReferenceEquals (W1, W2)); -False Console.ReadLine ();} Basic ReferenceEquals 

  

The ReferenceEquals method is called because the custom class widget overrides the virtual method of the object class equals; In addition, the class overrides the operator = = and! =, so the operation also returns True when the = = is executed. Therefore, calling ReferenceEquals can ensure that the return reference is equal.

3. Iequatable<t> interface

Call object. The Equals method actually boxed the value type that was being compared. In scenarios where performance is a high requirement, it is not appropriate to use this approach. Starting with c#2.0, we can solve this problem by introducing the Iequatable<t> interface.

public interface iequatable<t>{    BOOL Equals (T other);

When implementing the port of the IEquatable interface, invoking an interface method is equivalent to calling the Objet virtual method equals, but the interface method executes faster (no type conversions are required). Most. NET basic types implement the Iequatable<t> interface, you can also add iequatable<t> restrictions to the generic type

Internal class test<t> where t:iequatable<t>{public    bool IsEqual (t T1, T T2)    {        return t1. Equals (t2);}    }

If we remove the iequatable<t> restriction the,test<t> class can still be compiled, but T1. Equals (T2) will use object. The Equals method.

4. When the equals result is inconsistent with the result of = =

In the previous section, we have mentioned that sometimes, = = or equals is suitable for different scenarios. Like what:

Double x = Double. NaN; Console.WriteLine (x = = x); Falseconsole.writeline (X.equals (x)); True

This is because the = = operator of the double type forces nan to be equal to any other value, even if another nan. From a mathematical point of view, two are indeed unequal. And the Equals method, because it has symmetry, so x. Equals (x) always returns TRUE.

Collections and dictionaries depend on the symmetry of equals, otherwise they cannot find the elements that have been saved in the collection or dictionary.

For value types, equals and = = seldom have different equality. In reference types, it is more common. Generally, the creator of a reference type overrides the Equals method to perform a value equality comparison, while reserved = = Performs a reference equality comparison. For example, the StringBuilder class is like this:

StringBuilder buffer1 = new StringBuilder ("123"); StringBuilder buffer2 = new StringBuilder ("123"); Console.WriteLine (Buffer1 = = buffer2); Falseconsole.writeline (Buffer1. Equals (buffer2)); True

Comparing Custom Types

Review the default comparison behavior

    • Value types use value equality
    • Reference types use reference equality

Further

    • The Equals method of a struct type makes an equal row comparison based on the type of each field

Sometimes, when you create a type, you need to override the above behavior, typically in the following two scenarios:

    • Change the meaning of equality
    • Increase the speed of comparison of structure types

1) Change the meaning of equality

When the default = = and equals do not apply (do not conform to natural rules, or are contrary to the expectations of the consumer) to the custom type, you need to change the meaning of equality. For example, the DateTimeOffset structure, which has two private members: a DateTime type of UTC, and an int type of offset. If you are creating a DateTimeOffset type, you will most likely want the UTC field to be equal, instead of comparing the offset field. Another example is a number type that supports Nan, such as float and double, and if you create these two types, you might want Nan to be comparable.

For class types, many times, it is more meaningful to use a value comparison. In particular, some classes that contain less data, such as System.Uri or System.String

2) Improve the comparison speed of the structure type

The default comparison algorithm for struct types is relatively slow. By overriding the Equals method, you can improve performance by 5%. The overloaded = = operation and implementation iequatable<t> can achieve equality comparisons without boxing, which makes it possible to improve the performance of 5%.

For custom equality comparisons, there is a special case where Hashtable can achieve better performance after changing the hashing algorithm of the struct type. This is because both the hashing algorithm and the equality comparison occur on the stack.

3) How to override equality

In general, there are three ways to do this:

    • overriding GetHashCode () and Equals ()
    • "Optional" overload! = and = =
    • "Optional" Implementation iequatable<t>

I) Rewrite GetHashCode

The virtual method of object objects is GetHashCode, and is only useful for hashtable types and dictionary<tkey,tvalue> types.

Both types are Hashtable collections, and each element in the collection is a key value used to store the element and get the element. A hash table uses a specific policy to effectively allocate elements based on the key value of the element. This requires that each key value has a Int32 number (or hash code). The hash code is unique for each key value and must have good performance. The hash table considers that the GetHashCode method defined by the object class is sufficient, so both types omit the method of getting the hash code.

The GetHashCode method is implemented by default regardless of the value type or reference type, so you do not have to override this method unless you need to override the Equals method. (So if you rewrite the GetHashCode method, you're definitely going to need to rewrite the Equals method).

If you need to override the GetHashCode method, you can refer to the following rules:

    • If the Equals method returns True, the two objects that are compared must return the same hash code
    • Throwing exceptions is not allowed
    • Unless the object changes, duplicate calls to an object GetHashCode method should return the same hash code

To improve the performance of the hash table, GetHashCode needs to be rewritten to prevent different values from returning the same hash code. This explains why it is necessary to override the Equals and GetHashCode methods for struct types, so rewriting is more efficient than the default hashing algorithm. The default implementation of the GetHashCode method of a struct type occurs at run time and is likely to be implemented based on each member of the struct.

char typepublic override int GetHashCode () {    return (int) m_value | ((int) m_value << 16);} int32public override int GetHashCode () {    return m_value;}

In the case of class type, the default implementation of the GetHashCode method is based on the internal object identity, which is unique within the CLR for each object instance.

public virtual int GetHashCode () {    return runtimehelpers.gethashcode (this);}

II) override equals

Object. The provisions of the equal (axiom) are as follows:

    • An object cannot be equal to null (unless the object is of type nullable)
    • Equality is symmetric (an object equals itself)
    • Equality is exchangeable (if a equals B, then B is equal to a)
    • Equality is transitive (if a is equal to B,b equals C, then a equals C)
    • Equality is repeatable and reliable (no exceptions are thrown)

III) Overloading = = and! =

In addition to overriding equals, you can also overload the Equals and not equals operators.

For struct types, the basic overloads are the equals and not equal operators, and if they are not overloaded, the result of the error will be returned for the struct type, equal to and not equal to;

For class types, there are two ways to handle them:

    • = = and! = are not overloaded because they do reference equality
    • Reload = = and! = to make it consistent with equals

The first implementation applies to most custom types, especially variable (mutable) types. It ensures that custom types that conform to = = and! = should perform a comparison of referential equality so that these custom users are not misled. Once again, take a look at the StringBuilder example above.

StringBuilder buffer1 = new StringBuilder ("123"); StringBuilder buffer2 = new StringBuilder ("123"); Console.WriteLine (Buffer1 = = buffer2); False, Reference equalityconsole.writeline (buffer1. Equals (buffer2)); True, Value Equality

The second implementation applies to users who never want custom types to perform reference equality. Generally these types are immutable (immutable) types, such as String types and System.Uri types, and of course, some reference types.

III) Realization of iequatable<t>

To maintain integrity, it is recommended that you implement the Iequatable<t> interface while overriding the Equals method. The result of the interface method should be consistent with the results of the Equals method after the custom override. If you have overridden the Equals method, implementing iequatable<t> does not require additional implementation code (call the Equlas method directly)

Internal class staff:iequatable<staff>{Public    string FirstName {get; set;}    Implements iequatable<staff> public    bool Equals (staff Other)    {        return this. Firstname.equals (Other. FirstName);    }    Override equals public    override bool Equals (object obj)    {        if (obj = = null)            return this = = null;        if (! ( OBJ is the staff))            return false;        Staff s = obj as the staff;        return this. FirstName = = S.firstname;    }    Override GetHashCode public    override int GetHashCode ()    {        return this. Firstname.gethashcode ();    }            }

IV) An equal comparator that can be inserted

If you want a type to use a different comparison in a particular scenario, you can use a plug-in IEqualityComparer. It is particularly useful for collection classes. (Follow-up content Introduction)

Comparison Summary of equality

In the C # class library, three interfaces were designed for equality comparisons: Iequatable<t>,iequalitycomparer, and iequalitycomparer<t>.

The difference between IEqualityComparer and iequalitycomparer<t> is simple, a non-generic, need to convert T to object, then call the Equals method of object While the latter directly invokes the Equals method of the T type instance.

So what are the differences between iequatable<t> and iequalitycomparer<t>, and what are the different scenarios for each?

1. iequatable<t> is used to compare whether another object of the same type is equal, while iequalitycomparer<t> is used to compare two instances of the same type for equality.

2. If the two instances are equal only one possibility, or if there are several equal comparisons but only one of them is more meaningful, then the iequatable<t>,t type should be chosen to implement the Iequatable<t> interface itself. So the iequatable<t> instance knows how to compare itself to another instance. Conversely, if there are multiple equality comparisons between instances that need to be compared, iequalitycomparer<t> is better suited for this case; This interface is not implemented by the T type, but requires an external class implementation iequalitycomparer<t > interface. Because, when comparing two type instances for equality, because the T type does not know how to compare internally, you need to specify a iequalitycomparer<t> instance to perform equality comparisons to meet specific requirements.

3. Example

Internal class Staff:iequatable<staff> {public string FirstName {get; set;}            public string Title {get; set;}            public string Dept {get; set;} public override string ToString () {return string.            Format ("firstname:{0}, Title:{1}, Dept:{2}", FirstName, Title, Dept);                }//Implements Iequatable<staff> public bool Equals (staff other) { return this. Firstname.equals (Other.            FirstName); }//override Object.GetHashCode public override int GetHashCode () {retur n this.            Firstname.gethashcode (); }}internal class Stafftitlecomparer:iequalitycomparer<staff> {public bool Equals (staff            x, staff y) {return x.title = = Y.title;        } public int GetHashCode (Staff obj)    {return obj.            Title.gethashcode ();            }}internal class staffdeptcomparer:iequalitycomparer<staff>{public bool Equals (staff x, staff y)            {return x.dept = = y.dept; } public int GetHashCode (staff obj) {return obj.    Dept.gethashcode ();                }}static void Main (string[] args) {ilist<staff> staffs = new List<staff> { New Staff{firstname= "AAA", title= "Manager", dept= "Sale"}, New Staff{firstname= "BBB", title= "Ac                Countant ", dept=" Finance "}, New Staff{firstname=" BBB ", title=" Accountant ", dept=" Finance "}, New Staff{firstname= "AAA", title= "Sales", dept= "Sale"}, New Staff{firstname= "ABA", title= "Manager", dept= "H            R "}};            Print ("All Staffs", Staffs); Print ("No Duplicated First name", Staffs.            Distinct ()); Print ("No duplicated title", Staffs. Distinct (New Stafftitlecomparer ())); Print ("No duplicated Department", Staffs.            Distinct (New Staffdeptcomparer ()));        Console.ReadLine ();            }private static void Print (string group, ienumerable<staff> staffs) {Console.WriteLine (group);            foreach (Staff S-in staffs) Console.WriteLine (S.tostring ());        Console.WriteLine (); }overall

  

--update--

As a final example, you can also implement Distinctby by extending ienumeable<t>:

public static class ienurambleextension{public    static ienumerable<tsource> Distinctby<tsource, tkey>        (this ienumerable<tsource> source, Func<tsource, tkey> keyselector)    {         hashset<tkey> keys = new hashset<tkey> ();        foreach (tsource element in source)            if (keys. ADD (keyselector (Element)))                 yield return element;}    }

You can use this

Staffs. Distinctby (s = s), note that the staff class needs to implement iequatable<t> (or override Equals and GetHashCode)

Staffs. Distinctby (s = = s.dept), which eliminates the writing of Staffdeptcomparer class

Further, if a field of staff is a class, then this class also needs to implement iequatable<t> (or override Equals and GetHashCode)

Resources

1. C # 5.0 in a nutshell;

2. MSDN, Iequatable<t>, http://msdn.microsoft.com/en-us/library/ms131187.aspx;

3. MSDN IEqualityComparer, http://msdn.microsoft.com/en-us/library/ms132151.aspx;

4. StackOverflow, http://stackoverflow.com/questions/9316918/ What-is-the-difference-between-iequalitycomparert-and-iequatablet

C # Equality comparison (RPM)

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.