字典不使用类的比较方法



几天前,我为我编写的一个类提出了一个关于C#中字典实现的问题。我有一个字典,里面有类Interval的键和类Line2D的值。我更新的间隔实现如下:

public class Interval : ICloneable, IComparable, IComparable<Interval>, IComparable<double>
{
public Interval()
{
}
public Interval(Interval interval)
{
this.CopyFrom<Interval>(interval);
}
public Interval(double start, double end)
{
Start = start;
End = end;
}
// Properties
public double Start { get; set; } = double.NaN;
public double End { get; set; } = double.NaN;
public double Span => End - Start;
// Methods
public object Clone() => MemberwiseClone();
public int CompareTo(object obj)
{
if (obj is Interval iObj)
{
return CompareTo(iObj);
}
return 1;
}
public int CompareTo([AllowNull] Interval other)
{
if (Start == other.Start && End == other.End)
{
return 0;
}
else if (End <= other.Start)
{
return -1;
}
else if (other.End <= Start)
{
return 1;
}
else
{
throw new ArgumentException("Interval must not overlap with this one.", nameof(other));
}
// Old implementation
//if (Start < other.Start)
//{
//    return -1;
//}
//else if (Start > other.Start)
//{
//    return 1;
//}
//else
//{
//    return 0;
//}
}
public int CompareTo([AllowNull] double other)
=> Contains(other) ? 0 : (other < Start ? 1 : -1);
public bool Contains(double x) => Start <= x && x <= End;
public override string ToString() => $"[{Start}, {End}]";
}

因此,如果我创建一个关键字为Interval对象的字典,我认为我的CompareTo方法将涵盖两个区间具有相同起点和终点的情况。然而,事实并非如此。

var testDict = new Dictionary<Interval, int>();
var testInterval1 = new Interval(0, 1);
var testInterval2 = new Interval(testInterval1); // Should be identical
testDict[testInterval1] = 5;
var contains = testDict.ContainsKey(testInterval2); // This is false when it should be true;
testDict[testInterval2] = 10; // This shouldn't work but it does

为什么默认比较器在执行过程中不跳到我的CompareTo方法中?

因为Dictionary不使用IComparer<T>,所以它使用IEqualityComparer<T>

来自Dictionary<TKey,TValue>:官方文件的备注部分

Dictionary<TKey,TValue>需要一个相等实现来确定键是否相等。您可以通过使用接受比较器参数的构造函数来指定IEqualityComparer<T>泛型接口的实现;如果未指定实现,则使用默认的通用相等比较器EqualityComparer<T>.Default。如果类型TKey实现System.IEquatable<T>泛型接口,则默认的相等比较器将使用该实现。

请注意,不是您的类型需要实现IEqualityComparer<T>接口,而是可以将其作为依赖项传递给Dictionary的构造函数。

如果您希望以特定的方式比较您的类型,则应该在您的类型中实现IEquatable<T>接口。

注意:如果实现IEquatable<T>接口,建议还重写Equals(object)GetHashCode()方法,并重载==!=运算符,如"实现者注意事项:"中所述

IEquatable<T>接口的类型参数替换为实现该接口的类型。如果实现IEquatable<T>,还应该覆盖Equals(Object)GetHashCode()的基类实现,以便它们的行为与Equals(T)方法的行为一致。如果您确实重写了Equals(Object),那么在对类的静态Equals(System.Object, System.Object)方法的调用中也会调用您重写的实现。此外,您应该重载op_Equalityop_Inequality运算符。这样可以确保所有相等性测试都返回一致的结果。

要使用复数值作为字典键,需要专门实现GetHashCode()Equals()CompareTo与此用例无关。

我认为Dictionary使用了类的Equals和GetHashCode方法,所以需要重写它们。请参阅此问题

可比较!=相等的

相等的检查可以告诉你两个项目是否相等。可比较的检查可以告诉你哪个项目的排名比另一个高。

虽然可比性本质上需要可比性(因为如果你不能区分事物,你就无法对其进行排序(,但可比性并不需要可比。由于字典只关心将其键值相等,因此它完全忽略了Interval类是否具有可比性。

字典包含自己的相等比较器。默认情况下,它使用默认相等比较器,默认相等比较器依赖于IEquatable<T>而不是IComparable<T>

注意
MSDN声称它使用IEquatable<T>.Equals来检查相等性是不正确的。当您用字典测试这一点时,实际上是IEquatable<T>.GetHashCode用于检查字典键值之间的相等性

换句话说:字典检查键值是否(不(相等,它不会尝试对键值进行排序/排序。

这里有两种可能的解决方案:

  • Interval类上实现IEquatable<T>,并依赖字典的默认相等比较器
  • 为您的字典提供一个自定义的相等比较器,您可以在其中定义自己的相等比较逻辑(MSDN链接(

最新更新