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