实现地理坐标类:相等比较

Jon*_*Jon 11 c# geocoding equality

我正在将CodePlex的地理坐标类集成到我的个人"工具箱"库中.此类使用float字段来存储纬度和经度.

自从类GeoCoordinate实现以来IEquatable<GeoCoordinate>,我习惯性地编写了Equals这样的方法:

public bool Equals(GeoCoordinate other)
{
    if (other == null) {
        return false;
    }

    return this.latitude == other.latitude && this.longitude == other.longitude;
}
Run Code Online (Sandbox Code Playgroud)

在这一点上,我停下来,并认为我正在比较浮点变量的相等性,这通常是禁忌.我的思考过程大致如下:

  1. 我只能设想一次设置LatitudeLongitude属性,这意味着不会累积任何错误来搞砸我的比较.

  2. 另一方面,写(尽管没有意义)是可能的

    var geo1 = new GeoCoordinate(1.2, 1.2);
    var geo2 = new GeoCoordinate(1.2, 1.2);
    
    // geo1.Equals(geo2) will definitely be true, BUT:
    
    geo2.Latitude *= 10;
    geo2.Latitude /= 10;
    
    // I would think that now all bets are off
    
    Run Code Online (Sandbox Code Playgroud)

    当然,这不是我能想象到的,但如果类的公共接口允许它,那么它Equals应该能够处理它.

  3. 使用difference < epsilon测试比较相等性可以解决比较两个实例的问题,但会产生更多问题:

    • 如何使平等传递?听起来不可能.
    • 如何为比较相等的所有值生成相同的哈希码?

      让我们说epsilon = 0.11(随机例子).接下来GeoCoordinate { 1, 1 }需要相同的哈希码GeoCoordinate { 1.1, 1.1 }.但后者需要相同的哈希码GeoCoordinate { 1.2, 1.2 }.您可以看到它的发展方向:所有实例都需要具有相同的哈希码.

  4. 所有这一切的解决方案是创建GeoCoordinate一个不可变的类.这也可以解决GetHashCode问题:它基于纬度和经度(还有什么),如果它们是可变的,那么使用GeoCoordinate作为字典的键就会遇到麻烦.但是,使类不可变有其自身的缺点:

    • 你不能实例化和配置类的实例(WPF范例),这在某些情况下可能会很痛苦
    • 由于失去了无参数构造函数,序列化可能也会变得很痛苦(我不是.NET序列化专家,所以我在这里看到的细节很多)

你会建议采用哪种方法?很容易让这个类符合我现在的要求(只是让它变得不可变),但是有更好的方法吗?

编辑:我在上面的列表中添加了一个项目3,将前一个项目3移动到位置4.

我将允许更多时间来提供反馈,但现在我采用不可变的方法.这里struct可以看到与相关成员相关的(因为这就是现在的情况); 评论和建议超过欢迎.

Cod*_*aos 6

  • Equals主要用于词典,所以你应该比较浮动只有epsilon的指南不适用于此.不要试图将epsilon逻辑放入Equals.用户的工作就是根据需要做到这一点.证明与epsilon一致的唯一哈希函数与常量哈希函数进行比较相对容易.

  • 你的实现有一个问题:它不处理NaNs.您需要使用Equals而不是使用方法==的各个坐标Equals.

    public bool Equals(GeoCoordinate other)
    {
        if (other == null) {
            return false;
        }
    
        return this.latitude.Equals( other.latitude) && this.longitude.Equals(other.longitude);
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • "不要比较使用==但使用epsilon"指南是针对消费代码而不是针对实现代码.所以我实现了一个函数,它返回两个地理坐标之间的距离,并告诉用户将其用于他的epsilon比较.

  • 我肯定会让它变成不可变的(不确定是否为结构或类).它具有值语义,因此应该是不可变的.
    我通常使用类似Linq-to-Xml/ Linq-to-Json用于序列化的东西,因为这允许我在内存模型和磁盘模型之间转换表示.
    但你是对的,许多序列化程序不支持非默认构造函数.我认为这是那些序列化程序中的一个大缺陷,而不是我模型中的一个缺陷.有些序列化程序只是访问私有的setter/fields,但我个人认为这很糟糕.


Mar*_*ell 6

对我来说,经度/纬度的"位置"非常适合"不可变值"槽.该位置本身不会改变-如果改变纬度这是一个不同的位置.从那里,它可能是一个struct; 对于floatstruct会是同样大小为64参考无论如何,所以没有真正的一面.

重新平等; 如果位置不完全相同,那就不是"等于",至少在"关键"的角度来看,所以我对此感到高兴==.如果它有帮助,您可以添加"在(x)距离内"方法.当然,大弧几何也不是完全自由的; p

但是思考:

  • 它应该覆盖bool Equals(object)以及添加一个bool Equals(GeoCoordinate)
  • 它应该覆盖GetHashCode()并实施IEquatable<GeoCoordinate>
  • 静态运算符是一个很好的可选附加功能