Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 我应该使用struct还是class来表示Lat/Lng坐标?_C#_.net_Struct_Geocoding - Fatal编程技术网

C# 我应该使用struct还是class来表示Lat/Lng坐标?

C# 我应该使用struct还是class来表示Lat/Lng坐标?,c#,.net,struct,geocoding,C#,.net,Struct,Geocoding,我正在使用地理编码API,需要将返回点的坐标表示为纬度/经度对。但是,我不确定是使用struct还是类来实现此目的。我最初的想法是使用结构,但在C#中它们似乎普遍不受欢迎(例如,Jon Skeet提到,“我几乎从不定义自定义结构”)。性能和内存使用不是应用程序中的关键因素 到目前为止,我已经基于一个简单的接口提出了这两种实现: 接口 public interface ILatLng { double Lat { get; } double Lng { get; } } LatL

我正在使用地理编码API,需要将返回点的坐标表示为纬度/经度对。但是,我不确定是使用struct还是类来实现此目的。我最初的想法是使用结构,但在C#中它们似乎普遍不受欢迎(例如,Jon Skeet提到,“我几乎从不定义自定义结构”)。性能和内存使用不是应用程序中的关键因素

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

接口

public interface ILatLng
{
    double Lat { get; }
    double Lng { get; }
}
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));
    }
}
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);
    }
}
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));
    }
}
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);
    }
}
在执行一些测试时,我得到了以下发现:

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

  • 结构(作为值类型)永远不能为null,因此将始终包含值。但如果实现接口,您仍然可以执行以下操作:

    ILatLng s=newslatlng();s=零

在这种情况下,结构使用接口有意义吗

  • 如果使用结构,是否需要重写
    Equals
    GetHashCode()
    等?我的测试表明,如果不这样做,比较可以正常工作(与类不同)——那么有必要吗

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

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


感激地接受任何帮助或建议

老实说,我看不出有什么必要为此提供接口

我只想创建一个结构,但要使它不可变——可变结构是一个非常糟糕的主意。我还将使用完整的
纬度
经度
作为属性名称。大概是这样的:

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
并覆盖
Equals
GetHashCode
,例如

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();
}
请注意,您需要注意在双精度上执行相等比较的正常危险-这里没有太多选择,但是两个看起来应该相等的值可能不


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

我会使用结构。对于这里的简单使用来说,类是一种过分的杀伤力-您可以查看其他结构,例如Point。

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


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

在您的示例中不需要接口。只需将它变成一个普通的旧
结构
。这将防止在通过接口传递结构时出现任何不必要的装箱。

就我个人而言,我更喜欢使用类,即使它们是像LatLong这样的简单类。自从我的C++时代以来,我就没有使用过结构。类的另一个优点是,如果需要更复杂的功能,将来可以扩展它们

我确实同意这个线程中的其他人的看法,即接口似乎是一种滥杀滥伤,因为我没有您使用这个对象的完整上下文,因为它在您的应用程序中可能是必需的


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

为了提高性能,请将其设置为结构

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

    new SLatLng { Lat = 1.0, Lng = 2.0 }
    
接口使用成本 请注意,添加接口不可避免地会降低性能:接口无法定义字段,没有字段的结构几乎没有用处。这只剩下一个现实场景:接口要求您定义访问字段的属性

如果必须使用这些属性(通过getter/setter),则会降低直接访问的性能。比较:

带接口 生成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
无接口 生成直接赋值,并且(显然)没有装箱

// 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对象层次结构之外的实体,但每次定义结构时,系统也会定义一个从ValueType派生的伪类,其行为基本上与结构类似;加宽转换运算符是在结构和伪类之间定义的。请注意,声明为接口类型的变量、参数和字段始终作为类对象处理。如果某个东西在很大程度上被用作接口,在许多情况下,它也可能是一个类

尽管有些人抱怨可变结构的弊端,但在许多地方,如果不是因为系统处理它们的缺陷,具有值语义的可变结构将非常有用。例如:

  • 变异为“self”的方法和属性应标记为禁止在只读上下文中应用的属性;没有这种属性的方法应该是forb