你不知道的东西! c# == 等于运算符 和 Object.Equals()

最近在看 高级点的程序员必看的     CLR via C#    书中说解释了 Object.Equals()  方法的实现, 其中具体的实现用的是 == 运算符 !

以前就对 == 运算符 的具体实现  产生过疑惑 . 它到底对比的什么?

今天刚好手头的东西弄完了,而且还得强制加班中 !  所以就那今天的加班时间 来认真 来看一下 == 运算符 !

  • 最早对于 == 和 Object.Equals()  的了解是来源于 很早以前的一次面试 上面的面试题就有这个问题, 非常遗憾是 当时我水平有限 恰好不知道 怎么回答.  没有回答上来 回家后开始 百度一下答案 看的是迷迷糊  糊的. 有好几种说法. 当时也没太在意  找个了最主流的 就认为是 "真理" 了!
  • 阅读  CLR via C# 了解底层实现时 有看到了 Object.Equals()  看到了  内部使用的是 == 运算符 进行的比较   我对以前的标准答案 和 CLR via C# 书中所说的答案 都持有怀疑态度 然后找证据进行研究.我只相信我看到的. 我也庆幸自己 真的看到了.

因为是 Object.Equals() 这方法产生的疑问 所以吧这个内部实行帖出来

        public static bool Equals(object objA, object objB)
        {
            return ((objA == objB) || (((objA != null) && (objB != null)) && objA.Equals(objB)));
        }         //  他首先使用了 == 符号进行对比  然后又 使用对象自己 的 Equals 进行对比

Ps   objA.Equals方法得解释一下 .   objA使用的Equals方法是 类型自己可能会重写 父类的 Equals 的这个方法.

等于到底做了什么?

关于怎么实现的   非常抱歉的说  我并没有找到 == 运算符的具体实现代码.

但是 可以再MSDN 上看到 它到底干了什么.

http://msdn.microsoft.com/zh-cn/library/53k8ybth.aspx

对于预定义的值类型,如果操作数的值相等,则相等运算符 (==) 返回 true,否则返回 false。 对于 string 以外的引用类型,如果两个操作数引用同一个对象,则 == 返回 true。 对于 string 类型,== 比较字符串的值。

通过MSDN 上的解释 我们可以看到  好像是 它很厉害  能过自动通过类型 进行不同的对比.

其实并未如此

回到我们的更本问题  :  == 运算符 对比的到底什么?

如果你了解  栈  和 托管堆   那么你一定知道  数据 字节 一定不是放在 栈 中 就是放在 托管堆 中 所以 == 运算符 对比 不是对比的 栈 中的数据 就是对比的 托管堆中是数据 可以你觉得我说废话了,但是还是要交代清楚:

我们来看一段简单的代码

   class Program
    {
        static void Main(string[] args)
        {
            Class2 s1 = new Class2();
            Class2 s2 = new Class2();
            Console.WriteLine(s1 == s2 ? "True" : "false");
        }
    }
    public class Class2
    {
    }

大家猜一下  是  Ture 还是 False

答案是 False ;

想一下  如果是对比的是 托管堆中的话  那么他们都是创建了 该  对象的  "逐层父类"   "类型对象指针"  "同步快索引"  等 ;

代码中创建的是一样的 那么 托管堆中的数据就是一样的  如果是对比的 托管堆  那么返回就是 true  显然 程序返回的是 False

而且! 

值类型 是存放在 栈 中的 托管堆中并没有东西. 所以 对比 只能是对比 栈中的数据.

说了一堆 就是想先解释清楚 == 运算符对比的是 栈 为什么对比是 栈 而不是托管堆! 我喜欢说的详细点 虽然你可以能觉得啰嗦,我也得巧更多的键盘.但是我认为只有写的很详细了 观看的人才会容易理解.

下面做一些 MSDN 上 == 运算符 对比类型的解释

  • 值类型,MSDN上是 预定义的值类型  其实就是他本身只带的值类型 上面的结论是 == 对比是 栈 值类型的对比就没什么说的了
  • string 以外的引用类型  他们都有一个特定 那就是他们都是Object 的派生类.   栈中 放入的是 托管堆中的是 地址指向.   那么栈中是 地址指向  由于== 是对比的栈  后面的就不用说多了吧
  • string 类型   都说它是个特殊类型    我觉得是以为 微软对其做的  "stirng池"   定义一个 string  变量 并且给 它赋值的时候 会先检查  "stirng池" 中是是否已经有了 同样的的内存数据. 如果True 则 将这个内存地址 的指针 赋值给 栈中的变量名称.

string 类型 重写了 == 运算符 将其使用了 string 类重写的 Equals 方法进行对比;

具体代码段:

        public static bool Equals(string a, string b)
        {
            return ((a == b) || (((a != null) && (b != null)) && EqualsHelper(a, b)));
        }
        public static bool operator ==(string a, string b)
        {
            return Equals(a, b);
        }

        public static bool operator !=(string a, string b)
        {
            return !Equals(a, b);
        }

关键是 EqualsHelper  这个方法

 private static unsafe bool EqualsHelper(string strA, string strB)
        {
            int length = strA.Length;
            if (length != strB.Length)
            {
                return false;
            }
            fixed (char* str = ((char*) strA))
            {
                char* chPtr = str;
                fixed (char* str2 = ((char*) strB))
                {
                    char* chPtr2 = str2;
                    char* chPtr3 = chPtr;
                    char* chPtr4 = chPtr2;
                    while (length >= 12)
                    {
                        if (((*(((long*) chPtr3)) != *(((long*) chPtr4))) || (*(((long*) (chPtr3 + 4))) != *(((long*) (chPtr4 + 4))))) || (*(((long*) (chPtr3 + 8))) != *(((long*) (chPtr4 + 8)))))
                        {
                            break;
                        }
                        chPtr3 += 12;
                        chPtr4 += 12;
                        length -= 12;
                    }
                    while (length > 0)
                    {
                        if (*(((int*) chPtr3)) != *(((int*) chPtr4)))
                        {
                            break;
                        }
                        chPtr3 += 2;
                        chPtr4 += 2;
                        length -= 2;
                    }
                    return (length <= 0);
                }
            }
        }

它将其每个字符串全部取出单个进行对比.     所有 CLR via C# 中 说是对比的 同等性

现在 == 运算符的 问题都解开了疑惑了

那么让我们重新看一下 Object.Equals 这个方法

         public static bool Equals(object objA, object objB)
        {
            return ((objA == objB) || (((objA != null) && (objB != null)) && objA.Equals(objB)));
        }

这段代码我们前面就看过 是 Object.Equals 是代码,  让我们再次解释一下

  • 1 现将比对的对象封装成 跟类型 也就是Object  类  在说一遍封装 又得讲很多 简略一下   就是两个对象   如果是值类型 就将其通过 分装成Object 转换为引用类型 将其通过==运算符 对比栈中的 指向内存的指针.

                int s1 = 1;
                int s2 = 1;
                object o1 = (object) s1;
                object o2 = (object) s2;
                Console.WriteLine(o1==o2? "true":"fales");

    这段代码返回的永远都是 Fales

  • 2  然后进行 是否非空的验证
  • 3 调用 objA 对象的Equals 方法 . 这里是关键 !  因为这里调用的并不是 Object类的 Equals 方法 而是当前传递过来的对象类  类型的Equals 方法!
    几乎所有 类型都会重写 Object 根类的 Equals 即便是当前对象没有重写 那么父类也会重写Object 的 Equals 方法 如 所有值类型对象的根类  System.ValueType   重写了 Equals 方法
        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }
            RuntimeType type = (RuntimeType) base.GetType();
            RuntimeType type2 = (RuntimeType) obj.GetType();
            if (type2 != type)
            {
                return false;
            }
            object a = this;
            if (CanCompareBits(this))
            {
                return FastEqualsCheck(a, obj);
            }
            FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
            for (int i = 0; i < fields.Length; i++)
            {
                object obj3 = ((RtFieldInfo) fields[i]).InternalGetValue(a, false);
                object obj4 = ((RtFieldInfo) fields[i]).InternalGetValue(obj, false);
                if (obj3 == null)
                {
                    if (obj4 != null)
                    {
                        return false;
                    }
                }
                else if (!obj3.Equals(obj4))
                {
                    return false;
                }
            }
            return true;
        }

这是具体实现代码  看的很明显 是通过反射获取对象的值 进行对比 也就的对比的相等性

但是 ! 我们经常使用的 一些 .net预定义的类 几乎还会接着重写这个方法 

如 int32

        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
        public override bool Equals(object obj)
        {
            return ((obj is int) && (this == ((int) obj)));
        }

        [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
        public bool Equals(int obj)
        {
            return (this == obj);
        }

代码如上  可以清楚的看到 它并未  获取对比类型Type 搜索所以字段 属性 获取值对比等一系列复杂的 逻辑  就用了 ==  运算符进行的对比 为什么 你们自己想去 懒得说了. 系能是一方面 更本原因还是因为预定义的值类型 是栈中存放.

Ps:  写了这么多 观看的你 可以觉得我写的啰啰嗦嗦的, 但我还是认为写的详细点 把 真实的源码贴出来 才能彻底解决所产生的疑惑.  百度上看一眼 人家说的几句话你就能真的明白了嘛?

如有哪里不对 还希望指正!

本人渴望交流 和指点!

   

你不知道的东西! c# == 等于运算符 和 Object.Equals()

时间: 2024-08-15 21:16:01

你不知道的东西! c# == 等于运算符 和 Object.Equals()的相关文章

浅析Object基类提供的Virtual Object.Equals, Static Object.Equals and Reference.Equals等三个方法

当我们去查看object.cs源代码文件的时候,会发现object基类提供了三种判断相等性的方法.弄清楚每种方法存在的原因,也就是具体解决了什么问题,对我们理解.net判断对象相等性的逻辑很有帮助,下面让我们分别来看看吧! 1.Virtual Object.Equals()方法 实际上.net中提供了几种比较相等性(equality)的方法,但是最基础的方法就数object类中定义的virtual Object.Equals()了.下面让我们以一个customer类来看看该方法的实际运作. st

关于 warning CS0659:“***”重写Object.Equals(object o)但不重写Object.GetHashCode()

对象相等性和同一性 System.Object 类型提供了以下方法, 1 namespace System 2 { 3 // 4 // 摘要: 5 // 支持 .NET Framework 类层次结构中的所有类,并为派生类提供低级别服务.这是 .NET Framework 中所有类的最终基类:它是类型层次结构的根. 6 [ClassInterface(ClassInterfaceType.AutoDual)] 7 [ComVisible(true)] 8 public class Object

Object.equals() 方法

Object.equals() 方法: 1 public class EqualsTest1 { 2 public static void main(String[] args) { 3 //Cat c1 = new Cat(); 4 //Cat c2 = new Cat(); 5 //System.out.println(c1 == c2);//result:false 6 Cat c3 = new Cat(1,2,3); 7 Cat c4 = new Cat(1,2,3);//在重写equa

java中Object.equals()简单用法

/* equals()方法默认的比较两个对象的引用! */ class Child { int num; public Child(int x){ num = x; } //人文的抛出运行时异常的好处是:可以自定义错误信息! /*public boolean equals(Object o) throws ClassCastException{ if(!(o instanceof Child)) throw new ClassCastException("中文提示:类型错误"); Ch

讲的很详细的一篇关于object equals() &amp; hashCode() 的文章

转: 讲的很详细的一篇关于object equals() & hashCode() 的文章 哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: 1 public native int hashCode(); 根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现. 为何Object类需要这样一个方法?它有什么作用呢?今天我们就来具体探讨一下hashCode方

【Java编码准则】の #11不要使用Object.equals()来比较密钥值

java.lang.Object.equals()函数默认情况下是不能用来比较组合对象的,例如密钥值.很多Key类没有覆写equals()函数,因此,组合对象的比较必须单独比较里面的各个类型以保证正确性. [不符合安全要求的代码示例] 下面的代码使用equals()函数比较两个key值,key值即使具有相同的取值也可能会返回不相等,导致结果出错. private static boolean keysEqual(Key key1, Key key2) { if (key1.equals(key2

Java和C#中的接口对比(有你不知道的东西)

Java和C#中的接口对比(有你不知道的东西) 1.与Java不同,C#中的接口不能包含字段(Field). 在java中,接口中可以包含字段,但是这些字段隐式地是static和final的.而C#不允许接口中有字段,编译器在编译时就会提示错误(如下代码所示). interface IInterface { int a ;//编译器错误提示:接口不能包含字段 } 2.在C#和Java中,接口内的所有方法默认都是公用方法. 在Java中,方法声明可以带有public修饰符(即使这并非必要),但在C

object.Equals与object.ReferenceEquals方法

object.Equals方法表达的是语义判等,不一定是引用判等. object.ReferenceEquals方法是肯定是引用判等. 怎么实现一个对象的值语义的 Equals方法?实验. MyClass bool Equals(MyClass other) { // 1. other==null? // 2. 实际type是否相等 // 3. 简单类型的字段是否相等.调用引用类型的字段的Equals方法. } Notes: MyClass对象因的字段,以及引用字段又引用的字段,可能结果是递归遍

【Java编码准则】の #11不要使用Object.equals()来比較密钥值

java.lang.Object.equals()函数默认情况下是不能用来比較组合对象的,比如密钥值.非常多Key类没有覆写equals()函数,因此,组合对象的比較必须单独比較里面的各个类型以保证正确性. [不符合安全要求的代码演示样例] 以下的代码使用equals()函数比較两个key值,key值即使具有同样的取值也可能会返回不相等,导致结果出错. private static boolean keysEqual(Key key1, Key key2) { if (key1.equals(k