这篇文章主要介绍了c# 自定义值类型一定不要忘了重写Equals,帮助大家提高c# 程序的性能,感兴趣的朋友可以了解下
一:背景
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看一下。
二: 探究默认的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
也需要装箱一次。
public bool Equals(Point other)
{
return this.x == other.x && this.y == other.y;
}
三:真的解决问题了吗?
1. 遇到问题
很多时候我们会定义各种泛型类,在泛型操作中通常会涉及到T之间的 equals, 比如下面我设计的一段代码,为了方便,我把Point
的默认Equals也重写一下。
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)}");
Console.ReadLine();
}
}
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;
}
}
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;
}
}
我去,还是int🐮👃,貌似我的Point就比int少了接口实现,问题应该就出在这里,而且最后一个泛型接口IEquatable<int>特别显眼,看下定义:
public interface IEquatable<T>
{
bool Equals(T other);
}
这个泛型接口也仅仅只有一个equals
方法,不过灵感告诉我,貌似。。。也许。。。应该。。。就是这个泛型的equals
是用来解决泛型情况下的equals
比较。
3. 补上 IEquatable 接口
有了这个思路,我也跟FCL学,让Point实现 IEquatable<T>接口,然后在TProxy<T>代理类中约束下必须实现IEquatable<T>,修改代码如下:
public struct Point : IEquatable<Point> { ... }
public class TProxy<T> where T: IEquatable<T> { ... }
然后将程序跑起来,如下图:
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>
{}
然后我继续模仿List,把 TProxy<T>
上的T约束去掉,结果就出问题了,又回到了 通用Equals
。
var list = Enumerable.Range(0, 1000).Select(m => new Point(m, m)).ToList();
var item = list.Contains(new Point(int.MaxValue, int.MaxValue));
---------- outout ---------------
我是自定义的Equals
我是自定义的Equals
我是自定义的Equals
...
我也是太好奇了,翻看下 Contains
的源码,简化后实现如下。
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;
}
原来List是在进行 equals
比较之前,自己构建了一个泛型比较器EqualityComparer<T>
,🐮👃,然后继续追一下代码。
四:总结
一定要实现自定义值类型的 Equals
方法,人家的 Equals
方法是用来兜底的,一次比较两次装箱,对你的程序可是双杀哦😁😁😁。
以上就是c# 自定义值类型一定不要忘了重写Equals,否则性能和空间双双堪忧的详细内容,更多关于c# 自定义值类型的资料请关注得得之家其它相关文章!
本文标题为:c# 自定义值类型一定不要忘了重写Equals,否则性能和空间双双堪忧
- .NET CORE DI 依赖注入 2023-09-27
- Unity Shader实现模糊效果 2023-04-27
- C# 使用Aspose.Cells 导出Excel的步骤及问题记录 2023-05-16
- user32.dll 函数说明小结 2022-12-26
- 如何使用C# 捕获进程输出 2023-03-10
- Unity3D实现渐变颜色效果 2023-01-16
- c# 模拟线性回归的示例 2023-03-14
- Oracle中for循环的使用方法 2023-07-04
- WPF使用DrawingContext实现绘制刻度条 2023-07-04
- 在C# 8中如何使用默认接口方法详解 2023-03-29