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