你不知道的東西! c# == 等於運算子 和 Object.Equals()

atliwen發表於2014-06-13

 最近在看 高階點的程式設計師必看的     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:  寫了這麼多 觀看的你 可以覺得我寫的囉囉嗦嗦的, 但我還是認為寫的詳細點 把 真實的原始碼貼出來 才能徹底解決所產生的疑惑.  百度上看一眼 人家說的幾句話你就能真的明白了嘛?

如有哪裡不對 還希望指正!  

本人渴望交流 和指點!

 

   

相關文章