一:背景 1. 講故事 曾今在項目中發現有同事自定義結構體的時候,居然沒有重寫Equals方法,比如下麵這段代碼: static void Main(string[] args) { var list = Enumerable.Range(0, 1000).Select(m => new Point ...
一:背景
1. 講故事
曾今在項目中發現有同事自定義結構體的時候,居然沒有重寫Equals方法,比如下麵這段代碼:
static void Main(string[] args)
{
var list = Enumerable.Range(0, 1000).Select(m => new Point(m, m)).ToList();
var item = list.FirstOrDefault(m => m.Equals(new Point(int.MaxValue, int.MaxValue)));
Console.ReadLine();
}
public struct Point
{
public int x;
public int y;
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
}
這代碼貌似也沒啥什麼問題,好像大家平時也是這麼寫,沒關係,有沒有問題,跑一下再用windbg看一下。
0:000> !dumpheap -stat
Statistics:
MT Count TotalSize Class Name
00007ff8826fba20 10 16592 ConsoleApp6.Point[]
00007ff8e0055e70 6 35448 System.Object[]
00007ff8826f5b50 2000 48000 ConsoleApp6.Point
0:000> !dumpheap -mt 00007ff8826f5b50
Address MT Size
0000020d00006fe0 00007ff8826f5b50 24
0:000> !do 0000020d00006fe0
Name: ConsoleApp6.Point
Fields:
MT Field Offset Type VT Attr Value Name
00007ff8e00585a0 4000001 8 System.Int32 1 instance 0 x
00007ff8e00585a0 4000002 c System.Int32 1 instance 0 y
從上面的輸出不知道你看出問題了沒有? 托管堆上居然有2000個Point,而且還可以用 !do
打出來,說明這些都是引用類型。。。這些引用類型哪裡來的? 看代碼應該是 equals
比較時產生的,一次比較就有2個point被裝箱放到托管堆上,這下慘了,,,而且大家應該知道引用對象本身還有(8+8) byte
自帶開銷,這在時間和空間上都是巨大的浪費呀。。。
二: 探究預設的Equals實現
1. 尋找ValueType的Equals實現
為什麼會這樣呢? 我們知道equals
是繼承自ValueType
的,所以把 ValueType
翻出來看看便知:
public abstract class ValueType
{
public override bool Equals(object obj)
{
if (CanCompareBits(this)) {return FastEqualsCheck(this, obj);}
FieldInfo[] fields = runtimeType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
for (int i = 0; i < fields.Length; i++)
{
object obj2 = ((RtFieldInfo)fields[i]).UnsafeGetValue(this);
object obj3 = ((RtFieldInfo)fields[i]).UnsafeGetValue(obj);
...
}
return true;
}
}
從上面代碼中可以看出有如下三點信息:
<1> 通用的 equals
方法接收object類型,參數裝箱一次。
<2> CanCompareBits,FastEqualsCheck
都是採用object類型,this
也需要裝箱一次。
<3> 有兩種比較方式,要麼採用 FastEqualsCheck
比較,要麼採用反射
比較,我去.... 反射就玩大了。
綜合來看確實沒毛病, equals
會把比較的兩個對象都進行裝箱。
2. 改進方案
問題找到了,解決起來就簡單了,不走這個通用的 equals 不就行啦,我自定義一個equals方法,然後跑一下代碼。
public bool Equals(Point other)
{
return this.x == other.x && this.y == other.y;
}
可以看到走了我的自定義的Equals,