如何解决我应该使用结构或类来表示纬度/经度坐标吗?
| 我正在使用地理编码API,需要将返回点的坐标表示为纬度/经度对。但是,我不确定是否为此使用结构或类。我最初的想法是使用结构,但是在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));
}
}
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},this.Lng);
}
}
执行一些测试,我得出以下发现:
结构始终具有无参数的构造函数,这意味着您不能像使用类那样强制使用需要两个属性(对于lat和lng)的构造函数来实例化该结构。
结构(作为值类型)永远不能为null,因此将始终包含值。但是,如果实现接口,您仍然可以执行以下操作:
ILatLng s = new SLatLng(); s = null;
那么,在这种情况下,结构使用接口有意义吗?
如果使用结构,是否需要覆盖Equals
,GetHashCode()
等?我的测试表明,比较无需进行比较即可正常工作(与类不同)-因此有必要吗?
使用类让我感觉更“舒服”,所以当我更加了解它们的行为时,最好还是坚持使用它们?使用我的代码的人会被值类型的语义所迷惑吗,尤其是在使用接口时?
在CLatLng
实现中,覆盖GetHashCode()
似乎可以吗?我从本文中“偷了”它,所以不确定!
感激收到任何帮助或建议!
解决方法
老实说,我看不出为此有一个接口。
我只是创建一个结构,但使其不可变-可变结构是一个非常糟糕的主意。我还要使用完整的
Latitude
和Longitude
作为属性名称。像这样:
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>
并覆盖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();
}
请注意,您需要了解对double进行相等比较的正常危险-这里没有太多选择,但是看起来好像应该相等的两个值可能不会...
关于无参数构造函数的观点是合理的,但是我怀疑您会发现它实际上并不会咬您。
, 使其成为一个结构,以提高性能。
当您处理例如这些结构的数组。请注意,例如System.Collections.Generic.List可以正确处理.Net数组中元素类型的拆箱存储,因此它也适用于通用容器。
请注意,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;
}
}
生成设置器调用和装箱
.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对象层次结构之外的实体,但是每次定义结构时,系统还会定义一个从ValueType派生的伪类,该类在很大程度上类似于该结构。扩展转换运算符在struct和伪类之间定义。请注意,声明为接口类型的变量,参数和字段始终作为类对象进行处理。如果某种程度上将某种东西用作接口,那么在许多情况下,它也可能是一类。
尽管有人抱怨可变结构的弊端,但在很多地方,具有可变语义的可变结构将很有用,如果不是因为系统在处理它们时的不足之处。例如:
更改“自我”的方法和属性应使用禁止在只读上下文中应用的属性进行标记。除非使用兼容性开关进行编译,否则应禁止不具有此类属性的方法更改\“ self \\”。
应该有一种方法,可以通过将某些表达式内向外或者通过使用标准类型的property-delegate-pair来传递对结构或其字段的引用,以方便诸如myDictOfPoints(\“ George \”)。X ++之类的事情。
通常,值类型语义学比参考语义学要“期望的”更多,这不仅是因为前者在某些通用语言中的支持如此差。
PS:我建议,尽管可变结构通常是一件好事和适当的事情,但变异“自我”的结构成员处理不当,应避免使用。使用将返回新结构的函数(例如\“ AfterMoving(Double distanceMiles,Double headingDegrees)\”),该函数将返回一个新的LatLong,其位置将是在移动指定距离后的位置),或者使用静态方法(例如\“ MoveDistance(参考纬度长位置,Double distanceMiles,Double heading Degrees \”)。可变结构通常应在基本上代表一组变量的地方使用。
, 我会用一个结构。在这里,类是简单易用的方法,您可以看一下其他结构,例如Point。
, 您正在尝试制作一个可变的结构,这是不行的。特别是因为它实现了一个接口!
如果您想使LatLng
型可变,请坚持使用参考型。否则,struct对于此特定示例而言是合适的。
, 您的情况下不需要接口。只是把它做成普通的struct
。通过接口将when21ѭ通过时,这将防止任何不必要的装箱。
, 我真的很喜欢此坐标库在Codeplex上提供的证明和指导。在其中,他们使用类并表示lat的实际值,而使用float则表示它们的长值。
, 就个人而言,即使它们是像LatLong这样的简单类,我也喜欢使用它们。自C ++以来,我从未使用过结构。类的另一个优点是,如果需要更复杂的功能,将来可以扩展它们。
我确实同意该线程上的其他观点,尽管我不完全了解您正在使用的对象的上下文,但是在您的应用程序中可能需要一个接口,这似乎是一个过大的杀伤力。
最后,您的GetHashCode似乎是“ Lat * Long \”的美化方法。我不确定这是否安全。另外,如果您打算在应用程序中多次使用GetHashCode,我建议保持简化以提高该方法的性能。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。