这是我直到今天才注意到的事情。显然,常用的元组类的 .NET 实现(Tuple<T>
, Tuple<T1, T2>
等)导致拳击处罚对于值类型当执行基于相等的操作时。
以下是该类在框架中的实现方式(来源来自 ILSpy):
public class Tuple<T1, T2> : IStructuralEquatable
{
public T1 Item1 { get; private set; }
public T2 Item2 { get; private set; }
public Tuple(T1 item1, T2 item2)
{
this.Item1 = item1;
this.Item2 = item2;
}
public override bool Equals(object obj)
{
return this.Equals(obj, EqualityComparer<object>.Default);
}
public override int GetHashCode()
{
return this.GetHashCode(EqualityComparer<object>.Default);
}
public bool Equals(object obj, IEqualityComparer comparer)
{
if (obj == null)
{
return false;
}
var tuple = obj as Tuple<T1, T2>;
return tuple != null
&& comparer.Equals(this.Item1, tuple.Item1)
&& comparer.Equals(this.Item2, tuple.Item2);
}
public int GetHashCode(IEqualityComparer comparer)
{
int h1 = comparer.GetHashCode(this.Item1);
int h2 = comparer.GetHashCode(this.Item2);
return (h1 << 5) + h1 ^ h2;
}
}
我看到的问题是它会导致两阶段装箱-拆箱,例如Equals
电话,一,在comparer.Equals
将物品装箱,两个,EqualityComparer<object>
称为非通用的 Equals
这反过来又必须在内部将项目拆箱为原始类型。
相反,他们为什么不做类似的事情:
public override bool Equals(object obj)
{
var tuple = obj as Tuple<T1, T2>;
return tuple != null
&& EqualityComparer<T1>.Default.Equals(this.Item1, tuple.Item1)
&& EqualityComparer<T2>.Default.Equals(this.Item2, tuple.Item2);
}
public override int GetHashCode()
{
int h1 = EqualityComparer<T1>.Default.GetHashCode(this.Item1);
int h2 = EqualityComparer<T2>.Default.GetHashCode(this.Item2);
return (h1 << 5) + h1 ^ h2;
}
public bool Equals(object obj, IEqualityComparer comparer)
{
var tuple = obj as Tuple<T1, T2>;
return tuple != null
&& comparer.Equals(this.Item1, tuple.Item1)
&& comparer.Equals(this.Item2, tuple.Item2);
}
public int GetHashCode(IEqualityComparer comparer)
{
int h1 = comparer.GetHashCode(this.Item1);
int h2 = comparer.GetHashCode(this.Item2);
return (h1 << 5) + h1 ^ h2;
}
我很惊讶地看到在 .NET 元组类中以这种方式实现相等。我使用元组类型作为其中一本字典的键。
是否有任何理由必须如第一个代码所示来实现?在这种情况下使用此类有点令人沮丧。
我认为代码重构和非重复数据不应该是主要关注点。同样的非通用/拳击实现已经落后了IStructuralComparable
也是,但是自从IStructuralComparable.CompareTo
较少使用,这通常不是问题。
我用第三种方法对上述两种方法进行了基准测试,该方法仍然不那么费力,如下所示(仅是要点):
public override bool Equals(object obj)
{
return this.Equals(obj, EqualityComparer<T1>.Default, EqualityComparer<T2>.Default);
}
public bool Equals(object obj, IEqualityComparer comparer)
{
return this.Equals(obj, comparer, comparer);
}
private bool Equals(object obj, IEqualityComparer comparer1, IEqualityComparer comparer2)
{
var tuple = obj as Tuple<T1, T2>;
return tuple != null
&& comparer1.Equals(this.Item1, tuple.Item1)
&& comparer2.Equals(this.Item2, tuple.Item2);
}
对于几个Tuple<DateTime, DateTime>
字段 a 1000000Equals
来电。这是结果:
第一种方法(原始 .NET 实现)- 310 毫秒
第二种方法 - 60 毫秒
第三种方法 - 130 毫秒
默认实现比最佳解决方案慢大约 4-5 倍。