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

:cow::nose:，虽然是成功了，但有一个地方让我不是很舒服，就是上面的第二行代码，在 `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;
}```