# 自定义值类型一定不要忘了重写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;
}
}

CanCompareBits,FastEqualsCheck 都是采用object类型， this 也需要装箱一次。

### 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 proxy = new TProxy() { 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
{
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, IEquatable
{
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
{
bool Equals(T other);
}

### 3. 补上 IEquatable 接口

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

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

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

### 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 @default = EqualityComparer.Default;
for (int j = 0; j < _size; j++)
{
if (@default.Equals(_items[j], item)) {return true;}
}
return false;
}