.net 中的判等
前言
前幾天,同事在 .net 程序中,遇到一個(gè)很 “詭異” 的問題:明明兩個(gè)值是相等的,可偏偏卻不相等,這是怎么回事呢?
初遇問題
剛聽到這個(gè)問題時(shí),我是滿臉的不相信,怎么可能?但是親自調(diào)試一看,確實(shí)什么都相等,但確實(shí)返回的不相等。見鬼了?為了更近一步研究這個(gè)問題,我特意模仿原程序邏輯寫了一個(gè)簡單的測試程序。
示例代碼
using System.Collections.Generic;
using System.Linq;
namespace TestOperatorEqualEqual
{
class CClassTest
{
public int data1;
public int data2;
}
class Program
{
public static bool IsClassTestEqual(CClassTest lhs, CClassTest rhs)
{
return lhs == rhs;
}
static void Main(string[] args)
{
List<CClassTest> testList = new List<CClassTest>();
for (int idx = 0; idx < 100; ++idx)
{
testList.Add(new CClassTest { data1 = idx, data2 = idx+1 });
}
CClassTest target = new CClassTest{data1 = 50, data2 = 51 };
var bContained = testList.Any(item => { return IsClassTestEqual(item, target); });
System.Console.WriteLine(bContained ? "yes" : "no");
System.Console.ReadKey();
}
}
}
明明存在相等項(xiàng)(data1== 50, data2 == 51),但是 Any 卻返回了 false。是不是很詭異?你能一眼看出上面程序的問題嗎?
單純從 vs 中看源碼,通過調(diào)試查看,都看不出任何問題。怎么辦?一般從源碼看不出問題,就需要從更底層來看,對于 C++ 編寫的原生程序來說就是查看匯編代碼,對于 C# 編寫的程序,優(yōu)先查看 IL(Intermediate Language) 代碼。微軟提供了查看 IL 代碼的工具—— ildasm。
ildasm
可以使用 everything 在磁盤上搜索 ildasm 的位置。

在 ildasm 界面中,通過 file -> open 打開需要查看的程序,然后找到需要查看的方法,如下圖:

好家伙,直接調(diào)用了 ceq,看到這段 IL 代碼,我恍然大明白,原來是比較了兩個(gè)變量的地址!難怪即使兩個(gè)類的成員一模一樣,但是總返回 false 了!
趕緊谷歌一下 .net operator== msdn,發(fā)現(xiàn)了官方說明,鏈接如下:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/equality-operators。讀完發(fā)現(xiàn)自己讀書太少了!下圖截取自上面的官方文檔。

原來,== 對于引用類型來說(class),是比較兩個(gè)變量是否引用同一個(gè)對象,而不是比較兩個(gè)變量的成員是否相等。
修復(fù)
知道問題的本質(zhì)原因,修改起來就很簡單了。只需要按成員比較兩個(gè) CClassTest 實(shí)例的成員是否相等即可。
關(guān)鍵函數(shù)修改如下:
public static bool IsClassTestEqual(CClassTest lhs, CClassTest rhs)
{
return lhs.data1 == rhs.data1 && lhs.data2 == rhs.data2;
}
彩蛋
既然文檔里說了可以重載 == operator,那么就重載一下唄。這樣就不用額外提供一個(gè)比較函數(shù)了,直接使用 rhs == rhs 這種形式的比較就可以了。于是我大筆一揮,寫下了如下代碼:
class CClassTest
{
public int data1;
public int data2;
public static bool operator == (CClassTest lhs, CClassTest rhs)
{
if (lhs == null && rhs == null)
{
return true;
}
else if (lhs != null && rhs != null)
{
return lhs.data1 == rhs.data1 && lhs.data2 == rhs.data2;
}
else
{
return false;
}
}
public static bool operator != (CClassTest lhs, CClassTest rhs)
{
return !(lhs == rhs);
}
}
你能看出上面代碼的問題嗎?
對,上面的寫法會導(dǎo)致 stackoverflow!

那正確的寫法是什么樣的呢?
正確的寫法如下:
public static bool operator ==(CClassTest lhs, CClassTest rhs)
{
if ((object)lhs == null && (object)rhs == null)
{
return true;
}
else if ((object)lhs != null && (object)rhs != null)
{
return lhs.data1 == rhs.data1 && lhs.data2 == rhs.data2;
}
else
{
return false;
}
}
**說明:**高版本的
c#(應(yīng)該是c# 7開始支持的) 還支持使用is判斷一個(gè)變量是否為空。下面這種寫法更優(yōu)雅!
if (lhs is null && rhs is null) ...
源碼下載
csdn: https://download.csdn.net/download/xiaoyanilw/18335150
百度云盤鏈接: https://pan.baidu.com/s/1VmnqoflrbWXFjeQV7TrKTg 提取碼: dsqt
總結(jié)
每種語言都有自己獨(dú)特的規(guī)則,學(xué)習(xí)并適應(yīng),才能更好的使用它。
多讀書!
