C# 我应该使用struct还是class来表示Lat/Lng坐标?
我正在使用地理编码API,需要将返回点的坐标表示为纬度/经度对。但是,我不确定是使用struct还是类来实现此目的。我最初的想法是使用结构,但在C#中它们似乎普遍不受欢迎(例如,Jon Skeet提到,“我几乎从不定义自定义结构”)。性能和内存使用不是应用程序中的关键因素 到目前为止,我已经基于一个简单的接口提出了这两种实现: 接口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
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 }
.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派生的伪类,其行为基本上与结构类似;加宽转换运算符是在结构和伪类之间定义的。请注意,声明为接口类型的变量、参数和字段始终作为类对象处理。如果某个东西在很大程度上被用作接口,在许多情况下,它也可能是一个类 尽管有些人抱怨可变结构的弊端,但在许多地方,如果不是因为系统处理它们的缺陷,具有值语义的可变结构将非常有用。例如: