繁体   English   中英

我应该使用结构还是 class 来表示纬度/经度坐标?

[英]Should I use a struct or a class to represent a Lat/Lng coordinate?

我正在使用地理编码 API 并且需要将返回点的坐标表示为纬度/经度对。 但是,我不确定是否为此使用结构或 class。 我最初的想法是使用结构,但它们似乎在 C# 中通常不受欢迎(例如,Jon Skeet 在这个答案中提到,“我几乎从不定义自定义结构”)。 性能和 memory 的使用不是应用中的关键因素。

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

界面

public interface ILatLng
{
    double Lat { get; }
    double Lng { get; }
}

LatLng Class 实现

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));
    }
}

LatLng 结构实现

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);
    }
}

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

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

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

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

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

  • 如果我使用结构,是否需要覆盖EqualsGetHashCode()等? 我的测试表明比较可以正常工作而不这样做(与类不同) - 所以有必要吗?

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

  • CLatLng实现中, GetHashCode()的覆盖是否正常? 我从这篇文章中“偷”了它,所以我不确定!

感激地收到任何帮助或建议!

老实说,我看不出为此有一个界面有任何意义。

我只想创建一个结构,但让它不可变 - 可变结构是一个非常糟糕的主意。 我还会使用完整的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);
    }
}

然后我还会实现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();
}

请注意,您需要注意在双精度数上执行相等比较的正常危险 - 这里没有太多选择,但看起来应该相等的两个值可能不是......

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

使其成为一个结构,以提高性能。

  • 当您处理这些结构的 arrays 时,性能优势将成倍增加。 请注意,例如 System.Collections.Generic.List 可以正确处理.Net Arrays 中元素类型的未装箱存储,因此它也适用于通用容器。
  • 请注意,您不能拥有构造函数的事实完全被 C# 3.5+ 初始化器语法否定:

     new SLatLng { Lat = 1.0, Lng = 2.0 }

接口使用成本

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

如果您必须使用属性(通过 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;
    }
}

生成 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

无接口

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

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

生成直接分配和(显然)没有装箱

// 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

结构和值类型是 .net Object 层次结构之外的实体,但是每次定义结构时,系统还会定义一个派生自 ValueType 的伪类,它的行为在很大程度上类似于结构; 在结构和伪类之间定义了扩展转换运算符。 请注意,声明为接口类型的变量、参数和字段始终作为 class 对象处理。 如果某些东西要在很大程度上用作接口,在许多情况下,它也可能是 class。

.尽管有些人抱怨可变结构的弊端,但具有值语义的可变结构在很多地方都会非常有用。 例如:

  1. 变异“self”的方法和属性应该用一个属性来标记,该属性将禁止它们在只读上下文中应用; 除非使用兼容性开关编译,否则应禁止没有此类属性的方法改变“self”。
  2. 应该有方法通过将某些表达式从里到外或通过具有标准类型的属性委托对来传递对结构或其字段的引用,以便促进诸如 myDictOfPoints("George").X++; 之类的事情。

通常,值类型语义比引用语义更“令人期待”,这不仅仅是因为前者在某些通用语言中的支持如此之差。

PS-我建议虽然可变结构通常是一件好事和适当的事情,但改变“自我”的结构成员处理不当,应该避免。 要么使用将返回新结构的函数(例如“AfterMoving(Double distanceMiles, Double headingDegrees)”),该结构将返回一个新的 LatLong,其 position 是移动指定距离后的位置),或者使用 static 方法(例如“MoveDistance (参考 LatLong position,双距离英里,双航向度)“)。 可变结构通常应该用在它们本质上代表一组变量的地方。

我会使用一个结构。 class 对于此处的简单使用来说太过分了-您可以查看其他结构,例如 Point 。

您正在尝试创建一个可变结构,这是一个禁忌。 特别是因为它实现了一个接口!

如果您希望您的LatLng类型是可变的,请坚持使用引用类型。 否则 struct 对于这个特定的例子很好。

在您的情况下不需要接口。 把它变成一个普通的旧struct 这将在通过其接口传递struct时防止任何不必要的装箱。

我真的很喜欢这个坐标库在 codeplex 上提供的理由和指导。在其中,他们使用类并表示他们使用浮点数的 lat 和 long 的实际值。

就个人而言,我更喜欢使用类,即使它们是像 LatLong 这样简单的类。 自 c++ 天以来,我没有使用过结构。 如果需要更复杂的功能,类的额外优势是将来能够扩展它们。

我确实同意这个线程上的其他人的观点,即接口似乎有点过头了,因为我没有关于你使用这个 object 的完整上下文,因为你的应用程序可能需要它。

最后,您的 GetHashCode 似乎是一种美化的“Lat * Long”方式。 我不确定这是否是一个安全的赌注。 此外,如果您计划在应用程序中多次使用 GetHashCode,我建议您保持简单以提高方法的性能。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM