Categories
Uncategorized

Analyzing the equivalent C # 1

table of Contents

    Brief introduction

    Equal comparison value and reference type

    Equality comparison function and associated

    Compare string of equivalence and System.Uri

    Generic interface IEquatable

    Custom comparison method

    For example

    to sum up

Brief introduction

Recent looking at “C # in a nutshell” book, you can see that although the .NET Framework has some drawbacks and shortcomings, but on the whole the design is quite good. Here, we intend to compare between the two objects related to expound from the C # language.

Equal comparison value and reference type

In C #, we know that for different types of data, comparing different ways. The most typical is, the type of comparative value is the value of both are equal, but is more of a reference type is whether both refer to the same object. The following example you can see the difference between their two.

int v1 = 3, v2 = 3;
object r1 = v1;
object r2 = v1;
object r3 = r1;
Console.WriteLine($"v1 is equal to v2: {v1 == v2}");    // true
Console.WriteLine($"r1 is equal to r2: {r1 == r2}");    // false
Console.WriteLine($"r1 is equal to r3: {r1 == r3}");    // true

In this example, belong to the type int type values ​​which are 3 variables v1 and v2. You can see from the results output, the two really are equal. However, this reference for object types, even with data converted from an int (int packing by the data type), both of which are not the same references and therefore are not equal (i.e., row 6). But for it r3, r1 object reference are referred to, and thus r3 and r1 equal.

Although the value of the type Comparison by value, compared with a reference type data according to whether the reference. However, there are some special circumstances. A typical example is the string, and the string System.Uri. Although these two types of data type is a reference type (class essentially all), but the results on which has demonstrated equivalence determination value type and the like.

string s1 = "test";
string s2 = "test";
Uri u1 = new Uri("https://www.bing.com");
Uri u2 = new Uri("https://www.bing.com");
Console.WriteLine($"s1 is equal to s2: {s1 == s2}");    // true
Console.WriteLine($"u1 is equal to u2: {u1 == u2}");    // true

You can see, these two data types to break the rules given before. Although string comparison and System.Uri two classes are similar, but the two concrete realization of behavior is not the same. Then the different data types more specific process is how, and how to customize comparative embodiment will be discussed in a subsequent section. However, we first look at how to deal with equal logic in C #.

Equality comparison function and associated

In the C # language system, you can know all the whole class Object is the root class data types. As can be seen in the .NET Core 3.0 Object, related functions are equivalent judgment 4, wherein two members of the class methods, static member 2 is a method, as follows:

public virtual bool Equals(object? obj);
public virtual int GetHashCode();
public static bool ReferenceEquals(object? objA, object? objB);
public static bool Equals(object? objA, object? objB);

It may be noted that this and other information which is not exactly the same, the only difference is that the incoming parameter type is object? Rather than the object. This is mainly introduced in C # 8.0 version can be null reference type. Here it can be a null reference types are not the focus of this article, there can be as object to deal with.

Here we are introduced one by one these four functions:

    Class member method Equals. The role of this method is that the object that is currently used and the incoming object to be compared, if agreement is considered to be equal. The method is set to virtual, i.e., in a subclass can override this method.

    Class member method GetHashCode. This method is mainly used in the hash process, such as hash tables and dictionaries classes. For this function, it is a basic requirement, if two objects are deemed equal, they will return the same hash value. For different objects, the function does not require to be returned must different hash values, but want to return to a different hash values ​​as much as possible, to be able to distinguish between different data objects in the hash processing. The method as above, modified by the virtual keyword, can also be overridden in a subclass.

    Static member method ReferenceEquals. This method is mainly used to determine whether two objects point to the same reference. Can also be seen in the source code, which is essentially on the word: return objA == objB ;. Since this method is static and can not be rewritten.

    Static member method Equals. For this method, can also be seen from the source, the same is first determined whether the two references in not the same, method Equals reusable object is determined whether both are equal. Similarly, because the method is static, but also can not be rewritten.

Compare string of equivalence and System.Uri

Well, we come back to the original question, why string System.Uri performance and behavior and other reference types are not the same, but similar type and value. In fact, strictly speaking, string and System.Uri although the performance of the object to compare the value of similar type, but the details inside the two are not the same.

For the string is, in most cases, in which a copy of the program, a string will only be stored once, no matter how many new string variable, as long as its value is the same, then all references to the same memory address. So for string comparisons, which is still relatively quoted, but most of the same value is a reference to the same object.

The different System.Uri, for such class objects, the number of new objects on the heap will open up a number corresponding memory space and store data. However, in comparison, a first comparison reference comparison method used then the comparison value. That is, when not both of the references to the same object and then compare its value is equal to the (source).

string s1 = "test";
string s2 = "test";
Uri u1 = new Uri("https://www.bing.com");
Uri u2 = new Uri("https://www.bing.com");
Console.WriteLine($"s1 is equal to s2 by the reference: {Object.ReferenceEquals(s1, s2)}"); // true
Console.WriteLine($"s1 is equal to s2: {s1 == s2}");    // true
Console.WriteLine($"u1 is equal to u2 by the reference: {Object.ReferenceEquals(u1, u2)}"); // false
Console.WriteLine($"u1 is equal to u2: {u1 == u2}");    // true

Examples of the above can be seen that the two string variables point to the same data object (ReferenceEquals method is to determine whether two references refer to the same object, where you can see the return value is true). For System.Uri, the two variables do not point to the same object, both still equal equal subsequent determination However, this time can be seen at this time according to the value to determine whether both are equal.

Generic interface IEquatable

Can be seen from the above examples, C # whether the two objects to be judged by substantially equal Equals method. However, Equals method is also not a panacea, which is particularly reflected in the value of the type which.

Since the method requires the incoming parameters Equals type object. If the method is applied to the value types, the type of the value will cause the cast to type object, i.e. will packing (Boxing) once. Boxing and unboxing is generally more time-consuming and tends to decrease the efficiency. In addition, object type means that the class objects can be equal judgment and any other class objects, but in general, we determine whether an object is equal to the premise of the two objects are certainly the same class.

C # solution adopted is to use a generic interface IEquatable solved. IEquatable comprises two main methods, as follows:

public interface IEquatable
{
    bool Equals(T other);
}

And Object.Equals (object? Obj) compared to the internal functions of a generic method, if a data structure or class implements the interface and the like, then when calling Equals method, according to the most suitable type of principle, it will first call Equals (T other) methods within the IEquatable . This avoids value type packing operation.

Custom comparison method

In some cases, in order to better simulate real-world scenarios, we need to customize the comparison between two individuals. In order to achieve such a comparison method, there are usually three steps need to be completed:

    Override Equals (object obj) and GetHashCode () method;

    Overload operator == and! =;

    Achieve IEquatable method;

On the first point, these two functions must be rewritten. For the realization of Equals (object obj), then, if realized within the generic interface method can be considered where the method can be called directly. GetHashCode () is used to distinguish between different objects as possible, so if two objects are equal, the hash value which should be equal, this will have a better performance in the hash table and the dictionary class.

For the second and third points, it is not necessary, but in general, in order to better use, both of which need to be the best heavy-duty.

You can see, these three points are related to the logical comparison. In general, we tend to compare the core logic in a generic interface for other methods, you can by calling the method in a generic interface.

For example

Here, we take a small example. Imagine this scenario, the current machine learning more and more fiery, but can not do without talking about machine learning matrix operations. For the matrix, we can use a two-dimensional array to hold. In the field of mathematics, we determine whether two matrices are equal, is to determine each element in the two matrices are equal, that is, the value of the type of judgment way. In C #, due to the two-dimensional array is a reference type, it can not directly use the equivalence determination to achieve this goal. Therefore, we need to modify their way of judgment.

   public class Matrix : IEquatable
    {
        private double[,] matrix;

        public Matrix(double[,] m)
        {
            matrix = m;
        }

        public bool Equals([AllowNull] Matrix other)
        {
            if (Object.ReferenceEquals(other, null))
                return false;
            if (matrix == other.matrix)
                return true;
            if (matrix.GetLength(0) != other.matrix.GetLength(0) ||
                matrix.GetLength(1) != other.matrix.GetLength(1))
                return false;
            for (int row = 0; row < matrix.GetLength(0); row++)
                for (int col = 0; col < matrix.GetLength(1); col++)
                    if (matrix[row,col] != other.matrix[row,col])
                        return false;
            return true;
        }

        public override bool Equals(object obj)
        {
            if (!(obj is Matrix)) return false;
            return Equals((Matrix)obj);
        }

        public override int GetHashCode()
        {
            int hashcode = 0;
            for (int row = 0; row < matrix.GetLength(0); row++)
                for (int col = 0; col < matrix.GetLength(1); col++)
                    hashcode = (hashcode + matrix[row, col].GetHashCode()) % int.MaxValue;
                return hashcode;
        }

        public static bool operator == (Matrix m1, Matrix m2)
        {
            return Object.ReferenceEquals(m1, null) ? Object.ReferenceEquals(m2, null) : m1.Equals(m2);

        }
        public static bool operator !=(Matrix m1, Matrix m2)
        {
            return !(m1 == m2);

        }
    }
    
Matrix m1 = new Matrix(new double[,] { { 1, 2, 3 }, { 4, 5, 6 } });
Matrix m2 = new Matrix(new double[,] { { 1, 2, 3 }, { 4, 5, 6 } });

Console.WriteLine($"m1 is equal to m2 by the reference: {Object.ReferenceEquals(m1, m2)}");     // false
Console.WriteLine($"m1 is equal to m2: {m1 == m2}");    //true

Implementation in the logical comparison Equals (Matrix other). In this method, the matrix is ​​first determined whether the two references with a two-dimensional array, after determining the number of ranks are equal, then the final determination for each element. The entire core logic here. For Equals (object obj) and == and! = Directly call the Equals (Matrix other) method. Note that, when overloaded == symbol, not directly with m1 == null to determine whether the first object is empty, otherwise it is infinite loop to call the == operator overloaded functions. Requires the required reference function is determined, then, use the static method ReferenceEquals Object class is determined.

to sum up

Overall, equal comparison in C # is a reference to the rule: the value is a value type of comparison is equal, the comparison of the reference type is whether both reference the same object. In addition, this article describes some of the related functions and an equality and interfaces, functions and effects of these interfaces is to build a framework of the equality comparison. These functions and interfaces, not only can use the default comparison rules, but we can also customize compare rules. In the last article, we give an example to simulate a custom comparison rules of use. By this example, we can clearly see the custom implementation compare.

Leave a Reply