![](/img/trans.png)
[英]Should I use Enum, static Class, Dictionary or Struct to represent these “labeled floats” in C#?
[英]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;
那么在这种情况下结构使用接口是否有意义?
如果我使用结构,是否需要覆盖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();
}
请注意,您需要注意在双精度数上执行相等比较的正常危险 - 这里没有太多选择,但看起来应该相等的两个值可能不是......
关于无参数构造函数的观点是合理的,但我怀疑你会发现它实际上不会咬你。
使其成为一个结构,以提高性能。
请注意,您不能拥有构造函数的事实完全被 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。
.尽管有些人抱怨可变结构的弊端,但具有值语义的可变结构在很多地方都会非常有用。 例如:
通常,值类型语义比引用语义更“令人期待”,这不仅仅是因为前者在某些通用语言中的支持如此之差。
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.