C# 元组的IEqualityComparer

C# 元组的IEqualityComparer,c#,C#,我使用2、3和4位小数的元组,例如: (Decimal? x, Decimal? y, Decimal? z) p1 = (1.25m, 2.12m, 3.14m); (Decimal? x, Decimal? y, Decimal? z) p2 = (1.24m, 2.11m, 3.16m); 我需要通过比较每个Decimal?值来检查p1是否等于p2 为了比较两个十进制值,我有以下IEqualityComparer: public class DecimalToleranceEqua

我使用2、3和4位小数的元组,例如:

(Decimal? x, Decimal? y, Decimal? z) p1 = (1.25m, 2.12m, 3.14m);

(Decimal? x, Decimal? y, Decimal? z) p2 = (1.24m, 2.11m, 3.16m);
我需要通过比较每个
Decimal?
值来检查
p1
是否等于
p2

为了比较两个十进制值,我有以下
IEqualityComparer

  public class DecimalToleranceEqualityComparer : IEqualityComparer<Decimal?> {

    private readonly Decimal _tolerance;

    public DecimalToleranceEqualityComparer(Decimal tolerance) {
      _tolerance = tolerance;
    }

    public Boolean Equals(Decimal? x, Decimal? y) {

      if (!x.HasValue && !y.HasValue)
        return true;

      else if (!x.HasValue || !y.HasValue)
        return false;

      else
        return Math.Abs(x.Value - y.Value) <= _tolerance;

    }

    public Int32 GetHashCode(Decimal? obj) {
      return obj.GetHashCode();
    }

  }
公共类DecimalToleranceQualityComparer:IEqualityComparer{
专用只读十进制_公差;
公共十进制公差质量比较器(十进制公差){
_公差=公差;
}
公共布尔等于(十进制?x,十进制?y){
如果(!x.HasValue&&!y.HasValue)
返回true;
else如果(!x.HasValue | |!y.HasValue)
返回false;
其他的

return Math.Abs(x.Value-y.Value)我发现您的十进制比较器存在两个主要问题,这两个问题都源于“容差”的容差:

  • GetHashCode
    与'Equals'不兼容-两个“equal”的小数可能具有不同的哈希代码

  • Equals
    是不可传递的。假设您的公差为1。那么1.2和2.0将是“相等”,2.0和2.9将是“相等”,但1.2和2.9将不是“相等”。不可传递的相等可能有问题

  • 如果忽略容差,则可以对没有这些问题的小数(和元组)使用内置的相等

    但是,为了好玩,一个实现可以是使用您拥有的比较器(或默认的
    Double?
    comparer),并比较p1.Item1和p2.Item1,等等:

    public class DecimalTupleEqualityComparer : IEqualityComparer<(Decimal?,Decimal?,Decimal?)> {
    
        private readonly Decimal _tolerance;
        private readonly IEqualityComparer<Decimal?> _comparer;
    
        public DecimalTupleEqualityComparer(Decimal tolerance) : this(tolerance, null) {}
        public DecimalTupleEqualityComparer(Decimal tolerance, IEqualityComparer<Decimal?> comparer) {
            if (comparer==null)
            {
                _comparer = EqualityComparer<Decimal?>.Default;
            }
            else
            {
                _comparer = comparer;
            }
        }
    
        public Boolean Equals((Decimal?,Decimal?,Decimal?) x, (Decimal?,Decimal?,Decimal?) y) {
        
            foreach (var (v1,v2) in new[] {(x.Item1, y.Item1), (x.Item2, y.Item2),(x.Item3, y.Item3)})
                if (!_comparer.Equals(v1,v2))
                    return false;
            
            return true;
        }
    
        public Int32 GetHashCode((Decimal?,Decimal?,Decimal?)  obj) {
          return obj.Item1.GetHashCode() ^ obj.Item2.GetHashCode() ^ obj.Item2.GetHashCode();
        }
    }
    
    公共类DecimalTupleEqualityComparer:IEqualityComparer{
    专用只读十进制_公差;
    专用只读IEqualityComparer\u comparer;
    公共DecimalTupleEqualityComparer(十进制容差):此(容差,null){}
    公共十进制整数等式比较器(十进制公差、IEqualityComparer比较器){
    if(比较器==null)
    {
    _comparer=EqualityComparer.Default;
    }
    其他的
    {
    _比较器=比较器;
    }
    }
    公共布尔值等于((十进制?,十进制?,十进制?)x,(十进制?,十进制?,十进制?)y){
    foreach(var(v1,v2)在新[]{(x.Item1,y.Item1),(x.Item2,y.Item2),(x.Item3,y.Item3)}
    如果(!\u比较器等于(v1,v2))
    返回false;
    返回true;
    }
    公共Int32 GetHashCode((十进制?,十进制?,十进制?)对象){
    返回obj.Item1.GetHashCode()^obj.Item2.GetHashCode()^obj.Item2.GetHashCode();
    }
    }
    
    你的意思是
    (十进制x,十进制y,十进制z)p2=(1.24m,2.11m,3.16m);
    ?(你的问题中有
    p1
    )是的,刚刚更正。我需要比较两个元组<代码> P1<代码>和<代码> P2<代码>你有比较器。你只需要一个比较器。要么创建一个扩展方法,要么定义一个委托或者创建一个静态助手。你将如何解决你指出的2个问题?我需要使用一个宽容,因为我在我考虑的单元测试中使用它。2.21和2.22足够相等…我不知道有什么方法可以定义一个具有可传递公差的相等比较器。如果你只是在单元测试中使用它们,你可以在单元测试中进行比较,而不是定义一个相等比较器。也就是说,如果你将比较器用作查找键或需要及物性的东西。如果你在使用它时没有产生问题,那么它可能不需要被修复。我同意@DStanley。假设你能神奇地修复#2,你会如何修复#1?什么“规则”你能想出一个办法让两个哈希代码在两个小数点上相同,并且在公差范围内相等吗?你必须以某种方式对哈希代码进行规范化。看起来哈希代码会退化到最坏的情况:所有哈希代码都相等!这完全违背了哈希的目的。也许我能看到的唯一解决这个问题的方法是forc将所有的小数点精确到相同的精度,并摆脱容忍的概念;在这种情况下,您可以使其一致且具有确定性。您可以将每个值舍入到一定数量的小数点(也可以用于哈希代码),但你会遇到一些奇怪的情况,比如1.51和2.49相等,但2.49和2.51不相等。是的,这就是我通过“强制到相同精度”所想的。我想我可以在这里使用“圆形:)但是,它回到了你提到的同一个问题。