自定义值类型一定不要忘了重写Equals,否则性能和空间双双堪忧

网友投稿 817 2022-09-16

自定义值类型一定不要忘了重写Equals,否则性能和空间双双堪忧

自定义值类型一定不要忘了重写Equals,否则性能和空间双双堪忧

一:背景

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,

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:office online server over exchange2019_20220302
下一篇:API接口常用签名加密方式及示例代码
相关文章

 发表评论

暂时没有评论,来抢沙发吧~