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

## 一：背景

### 1. 讲故事


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)));
}

public struct Point
{
public int x;
public int y;

public Point(int x, int y)
{
this.x = x;
this.y = y;
}
}




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
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



## 二: 探究默认的Equals实现

### 1. 寻找ValueType的Equals实现


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 比较，要么采用反射比较，我去.... 反射就玩大了。

### 2. 改进方案

        public bool Equals(Point other)
{
return this.x == other.x && this.y == other.y;
}


## 三：真的解决问题了吗？

### 1. 遇到问题


class Program
{
static void Main(string[] args)
{

var p1 = new Point(1, 1);
var p2 = new Point(1, 1);

TProxy<Point> proxy = new TProxy<Point>() { Instance = p1 };

Console.WriteLine(\$"p1==p2 {proxy.IsEquals(p2)}");
}
}

public struct Point
{
public int x;
public int y;

public Point(int x, int y)
{
this.x = x;
this.y = y;
}

public override bool Equals(object obj)
{
Console.WriteLine("我是通用的Equals");
return base.Equals(obj);
}

public bool Equals(Point other)
{
Console.WriteLine("我是自定义的Equals");
return this.x == other.x && this.y == other.y;
}
}

public class TProxy<T>
{
public T Instance { get; set; }

public bool IsEquals(T obj)
{
var b = Instance.Equals(obj);

return b;
}
}



### 2. 从FCL的值类型实现上寻找问题


public struct Int32 : IComparable, IFormattable, IConvertible, IComparable<int>, IEquatable<int>
{
public override bool Equals(object obj)
{
if (!(obj is int))
{
return false;
}
return this == (int)obj;
}

public bool Equals(int obj)
{
return this == obj;
}
}




public interface IEquatable<T>
{
bool Equals(T other);
}



### 3. 补上 IEquatable 接口


public struct Point : IEquatable<Point> { ...  }
public class TProxy<T> where T: IEquatable<T> { ... }



🐮👃，虽然是成功了，但有一个地方让我不是很舒服，就是上面的第二行代码，在 TProxy<T> 处约束了T，因为我翻看List的实现也没做这样的泛型约束呀，可能有点强迫症吧，贴一下代码给大家看看。


public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>
{}



### 4. 从List的Contains源码中寻找答案


var list = Enumerable.Range(0, 1000).Select(m => new Point(m, m)).ToList();
var item = list.Contains(new Point(int.MaxValue, int.MaxValue));

---------- outout ---------------

...




public bool Contains(T item)
{
...
EqualityComparer<T> @default = EqualityComparer<T>.Default;
for (int j = 0; j < _size; j++)
{
if (@default.Equals(_items[j], item)) {return true;}
}
return false;
}



## 四：总结

### 如您有更多问题与我互动，扫描下方进来吧~

posted @ 2020-05-31 08:39  一线码农  阅读(1565)  评论(8编辑  收藏