C # .NET offers many ways to compare objects, both instances of classes and structures. There are so many ways that without streamlining these methods and understanding their proper use and implementation (if it is possible to redefine), inevitably, porridge is formed.
So, the System.Object class offers the following methods:
public static bool ReferenceEquals(object objA, object objB) { return objA == objB; }
public static bool Equals(object objA, object objB) { return objA == objB || (objA != null && objB != null && objA.Equals(objB)); }
public virtual bool Equals(object obj) { return RuntimeHelpers.Equals(this, obj); }
And of course:
public static bool operator == (Foo left, Foo right);
It is also possible to inherit IEquatable, IStructuralEquatable.
ReferenceEquals
The ReferenceEquals method compares two links. If object references are identical, then returns true. This means that this method checks instances not for equality, but for identities. In the case of passing to this method instances of a meaningful type (even if you pass the same instance) will always return false. This will happen because during the transfer there will be a packaging of significant types and references to them will be different.
Here I would also like to mention the comparison of two lines by this method. For example:
class Program { static void Main(string[] args) { string a = "Hello"; string b = "Hello"; if(object.ReferenceEquals(a,b)) Console.WriteLine("Same objects"); else Console.WriteLine("Not the same objects"); Console.ReadLine(); } }
Such a program can easily display "Same objects". Do not worry, this is due to the internment of strings. But this is a completely different story and there will be no talk about this here.
public static bool Equals (object objA, object objB)
First, this method checks instances for identities, and if the objects are not identical, then checks them for null and delegates responsibility for comparing to the redefined instance method Equals.
')
public virtual bool Equals (object obj)
By default, this method behaves exactly the same as ReferenceEquals. However, for significant types it is redefined and in System.ValueType looks like this:
public override bool Equals(object obj) { if (obj == null) { return false; } RuntimeType runtimeType = (RuntimeType)base.GetType(); RuntimeType left = (RuntimeType)obj.GetType(); if (left != runtimeType) { return false; } if (ValueType.CanCompareBits(this)) { return ValueType.FastEqualsCheck(this, obj); } FieldInfo[] fields = runtimeType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); for (int i = 0; i < fields.Length; i++) { object obj2 = ((RtFieldInfo)fields[i]).InternalGetValue(this, false); object obj3 = ((RtFieldInfo)fields[i]).InternalGetValue(obj, false); if (obj2 == null) { if (obj3 != null) { return false; } } else { if (!obj2.Equals(obj3)) { return false; } } } return true; }
God forbid anyone to use such implementation on large sets. BCL developers cannot know which significant types we will determine and compare instances of significant types by their fields, using reflection, without knowing anything about these fields in advance. Of course, this is not a very productive way to compare. Therefore, when using the significant types known at the compilation stage, it is necessary to redefine this method, because who can know better than you how to compare two objects you have developed? For reference types, without the need to compare two instances in the manner of significant types, it is not necessary to override this method.
Let's look at an example of a competent redefinition of this method and immediately implement IEquatable:
class Vehicle:IEquatable<Vehicle> { protected int speed; public int Speed { get { return this.speed; } set { this.speed = value; } } protected string name; public string Name { get { return this.name; } set { this.name = value; } } public Vehicle(){} public Vehicle(int speed, string name) { this.speed = speed; this.name = name; } public override bool Equals(object other) {
A comment about the top of the hierarchy in the redefinition of the virtual method is not just made. If you create a successor of the Vehicle class (for example, Bike), which will also have the overridden virtual method Equals, in which there will be no type comparison by GetType, but there will be an attempt to cast the type
Bike tmp = other as Bike; if(tmp!=null) this.Equals(tmp);
Bike tmp = other as Bike; if(tmp!=null) this.Equals(tmp);
in this case, the following code may cause problems:
Vehicle vehicle = new Vehicle(); Bike bike = new Bike(); object vehicleObj = vehicle; object bikeObject = bike; bike.Equals(vehicleObj);
public static bool operator == (foo left, foo right)
For meaningful types, you should always redefine as virtual Equals (). For reference types, it is better not to override, for, by default, behavior from == on reference types is expected to behave as in the ReferenceEquals () method. So, everything is simple.
Istructuralequatable
IStructuralEquatable goes hand in hand with the IEqualityComparer interface. The IStructuralEquatable interface is implemented by classes such as System.Array or System.Tuple. As Bill Wagner writes, IStructuralEquality declares that a type can constitute larger objects that implement the semantics of significant types and hardly ever need to implement it ourselves. Although what is difficult in its implementation? Just look at its implementation in System.Array:
bool IStructuralEquatable.Equals(object other, IEqualityComparer comparer) { if (other == null) { return false; } if (object.ReferenceEquals(this, other)) { return true; } Array array = other as Array; if (array == null || array.Length != this.Length) { return false; } for (int i = 0; i < array.Length; i++) { object value = this.GetValue(i); object value2 = array.GetValue(i); if (!comparer.Equals(value, value2)) { return false; } } return true; }
Actually, first the identity of objects is checked, then they are reduced to the same type and compared in length. If the length is equal, then an element-by-element comparison starts by delegating responsibility for this comparison to the interface (IEqualityComparer) Equals method.
Here, in essence, is all that can be said about comparing objects in C # .NET, but one more small but important detail remains: the GetHashCode () method.
public virtual int GetHashCode ()
In general, the standard implementation of this method behaves as a unique identifier generator. The disadvantage of this approach is that identical semantically objects can return different hash values. Richter complains that the standard implementation is also low-performing. Competent implementation of this method is very problematic. It is necessary to calculate hash quickly and have a large scatter in the result so that repetitions on large enough sets do not happen. In fact, in most cases, the implementation of GetHashCode () is very simple. Everywhere shifts are made, “bitwise or”, or “exclusive or”. Richter himself gives an example with a structure that has two fields of type int. GetHashCode () it offers to implement something like this:
internal sealed class Point { private int a; private int b; public override int GetHashCode() { return a ^ b; } }
And this is how GetHasCode () is overridden in System.Char:
public override int GetHashCode() { return (int)this | (int)this << 16; }
Many examples can be cited and heuristic indicators are used almost everywhere for shifts that exclude or so on.
While writing the article, well-known sources were used:
J. Richter, CLR via C #B. Wagner Effective C #It also used its experience and sources on the Internet, which does not make much sense.
Translation
here