我应该使用结构或类来表示Lat/Lng坐标吗?

Dan*_*plo 37 .net c# struct geocoding

我正在使用地理编码API,需要将返回点的坐标表示为纬度/经度对.但是,我不确定是否为此使用结构或类.我最初的想法是使用一个结构,但它们似乎在C#中通常不赞成(例如,Jon Skeet 在这个答案中提到"我几乎从不定义自定义结构").性能和内存使用不是应用程序中的关键因素.

到目前为止,我已经基于一个简单的接口提出了这两个实现:

接口

public interface ILatLng
{
    double Lat { get; }
    double Lng { get; }
}
Run Code Online (Sandbox Code Playgroud)

LatLng类实现

public class CLatLng : ILatLng
{
    public double Lat { get; private set; }
    public double Lng { get; private set; }

    public CLatLng(double lat, double lng)
    {
        this.Lat = lat;
        this.Lng = lng;
    }

    public override string ToString()
    {
        return String.Format("{0},{1}", this.Lat, this.Lng);
    }

    public override bool Equals(Object obj)
    {
        if (obj == null)
            return false;

        CLatLng latlng = obj as CLatLng;
        if ((Object)latlng == null)
            return false;

        return (this.Lat == latlng.Lat) && (this.Lng == latlng.Lng);
    }

    public bool Equals(CLatLng latlng)
    {
        if ((object)latlng == null)
            return false;

        return (this.Lat == latlng.Lat) && (this.Lng == latlng.Lng);
    }


    public override int GetHashCode()
    {
        return (int)Math.Sqrt(Math.Pow(this.Lat, 2) * Math.Pow(this.Lng, 2));
    }
}
Run Code Online (Sandbox Code Playgroud)

LatLng Struct实现

public struct SLatLng : ILatLng
{
    private double _lat;
    private double _lng;

    public double Lat
    {
        get { return _lat; }
        set { _lat = value; }
    }

    public double Lng
    {
        get { return _lng; }
        set { _lng = value; }
    }

    public SLatLng(double lat, double lng)
    {
        this._lat = lat;
        this._lng = lng;
    }

    public override string ToString()
    {
        return String.Format("{0},{1}", this.Lat, this.Lng);
    }
}
Run Code Online (Sandbox Code Playgroud)

进行一些测试我得出以下发现:

  • struct总是有一个无参数的构造函数,这意味着你不能强制它用一个构造函数来实例化,这个构造函数需要两个属性(对于lat和lng),就像你可以使用一个类一样.

  • struct(作为值类型)永远不能为null,因此将始终包含值.但是如果实现一个接口,你仍然可以做这样的事情:

    ILatLng s = new SLatLng(); s = null;

那么在这种情况下结构使用接口是否有意义?

  • 如果我使用结构,我需要覆盖Equals,GetHashCode()等等?我的测试表明比较工作没有这样做(与课程不同) - 所以有必要吗?

  • 我觉得使用课程更"舒服",所以最好坚持使用它们,因为我更了解它们的行为方式?使用我的代码的人是否会被值类型语义所困惑,尤其是在使用接口时?

  • CLatLng实现中,覆盖GetHashCode()看起来好吗?我从这篇文章中偷了它,所以我不确定!

任何帮助或建议感激不尽!

Jon*_*eet 56

老实说,我无法看到为此设置界面有任何意义.

我只是创建一个结构,但让它不可变 - 可变结构是一个非常糟糕的主意.我也使用full LatitudeLongitude作为属性名称.像这样的东西:

public struct GeoCoordinate
{
    private readonly double latitude;
    private readonly double longitude;

    public double Latitude { get { return latitude; } }
    public double Longitude { get { return longitude; } }

    public GeoCoordinate(double latitude, double longitude)
    {
        this.latitude = latitude;
        this.longitude = longitude;
    }

    public override string ToString()
    {
        return string.Format("{0},{1}", Latitude, Longitude);
    }
}
Run Code Online (Sandbox Code Playgroud)

那么我实现IEquatable<GeoCoordinate>并覆盖EqualsGetHashCode,如

public override bool Equals(Object other)
{
    return other is GeoCoordinate && Equals((GeoCoordinate) other);
}

public bool Equals(GeoCoordinate other)
{
    return Latitude == other.Latitude && Longitude == other.Longitude;
}

public override int GetHashCode()
{
    return Latitude.GetHashCode() ^ Longitude.GetHashCode();
}
Run Code Online (Sandbox Code Playgroud)

请注意,您需要了解对双精度执行相等比较的正常危险 - 这里没有太多替代方案,但两个看起来应该相等的值可能不是......

关于无参数构造函数的观点是合理的,但我怀疑你会发现它实际上不会咬你.

  • @almcnicoll:首先请参见http://stackoverflow.com/questions/441309/why-are-mutable-structs-evil。 (2认同)
  • @NickN.:如果我在一个可以使用该类的环境中,我*可能会*使用它 - 但不一定(因为它包含了我知道我不会拥有的其他信息). (2认同)

seh*_*ehe 9

为了性能,使它成为一个结构.

  • 当您处理这些结构的数组时,性能优势将倍增.请注意,例如System.Collections.Generic.List正确处理.Net Arrays中元素类型的未装箱存储,因此它也适用于通用容器.
  • 请注意,C#3.5+ intializer语法完全否定了您无法使用构造函数的事实:

    new SLatLng { Lat = 1.0, Lng = 2.0 }
    
    Run Code Online (Sandbox Code Playgroud)

接口使用成本

请注意,添加接口不可避免地会降低性能:接口无法定义字段,没有字段的结构几乎不会有用.这只留下一个现实场景:界面要求您定义访问字段的属性.

如果您有义务使用这些属性(通过getter/setter),您将失去直接访问的性能.相比:

有了界面

public class X
{
    interface ITest { int x {get; } }
    struct Test : ITest
    {
        public int x { get; set; }
    }

    public static void Main(string[] ss)
    {
        var t = new Test { x=42 };
        ITest itf = t;
    }
}
Run Code Online (Sandbox Code Playgroud)

生成setter调用和装箱

.method public static  hidebysig 
       default void Main (string[] ss)  cil managed 
{
    // Method begins at RVA 0x20f4
.entrypoint
// Code size 29 (0x1d)
.maxstack 4
.locals init (
    valuetype X/Test    V_0,
    class X/ITest   V_1,
    valuetype X/Test    V_2)
IL_0000:  ldloca.s 0
IL_0002:  initobj X/Test
IL_0008:  ldloc.0 
IL_0009:  stloc.2 
IL_000a:  ldloca.s 2
IL_000c:  ldc.i4.s 0x2a
IL_000e:  call instance void valuetype X/Test::set_x(int32)
IL_0013:  ldloc.2 
IL_0014:  stloc.0 
IL_0015:  ldloc.0 
IL_0016:  box X/Test
IL_001b:  stloc.1 
IL_001c:  ret 
} // end of method X::Main
Run Code Online (Sandbox Code Playgroud)

没有界面

public class Y
{
    struct Test
    {
        public int x;
    }

    public static void Main(string[] ss)
    {
        var t = new Test { x=42 };
        Test copy = t;
    }
}
Run Code Online (Sandbox Code Playgroud)

生成直接分配和(显然)没有拳击

// method line 2
.method public static  hidebysig 
       default void Main (string[] ss)  cil managed 
{
    // Method begins at RVA 0x20f4
.entrypoint
// Code size 24 (0x18)
.maxstack 2
.locals init (
    valuetype Y/Test    V_0,
    valuetype Y/Test    V_1,
    valuetype Y/Test    V_2)
IL_0000:  ldloca.s 0
IL_0002:  initobj Y/Test
IL_0008:  ldloc.0 
IL_0009:  stloc.2 
IL_000a:  ldloca.s 2
IL_000c:  ldc.i4.s 0x2a
IL_000e:  stfld int32 Y/Test::x
IL_0013:  ldloc.2 
IL_0014:  stloc.0 
IL_0015:  ldloc.0 
IL_0016:  stloc.1 
IL_0017:  ret 
} // end of method Y::Main
Run Code Online (Sandbox Code Playgroud)