I am working a with a geo-coding API and need to represent the coordinate of a returned point as a Latitude / Longitude pair. However, I am unsure whether to use a struct or a class for this. My initial thought was to use a struct, but they seem to be generally frowned upon in C# (for instance, Jon Skeet mentions in this answer that, "I almost never define custom structs"). Performance and memory usage are not critical factors in the application.
So far I have come up with these two implementations based on a simple interface:
Interface
public interface ILatLng { double Lat { get; } double Lng { get; } }
LatLng Class Implementation
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 Struct Implementation
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); } }
Performing some tests I've come to the following findings:
A struct always has a parameterless constructor, which means you can't force it to be instantiated with a constructor which expects two properties (for lat and lng), as you can with a class.
A struct (being a value type) can never be null, so will always contain a value. But you can still do stuff like this if implementing an interface:
ILatLng s = new SLatLng(); s = null;
So does it make sense for a struct to use an interface in this case?
If I use a struct do I need to override Equals
, GetHashCode()
etc. ? My tests indicate comparisons work correctly without doing so (unlike with a class) - so is it necessary?
I feel more 'comfortable' using classes, so is it best to just stick with them as I'm more aware of how they behave? Will people using my code be confused by value-type semantics, especially when working to an interface?
In the CLatLng
implementation, does the override of GetHashCode()
seem OK? I 'stole' it from this article, so am unsure!
Any help or advice gratefully received!
Here are examples of formats that work: Decimal degrees (DD): 41.40338, 2.17403. Degrees, minutes, and seconds (DMS): 41°24'12.2"N 2°10'26.5"E. Degrees and decimal minutes (DMM): 41 24.2028, 2 10.4418.
Coordinates are written in degrees, then minutes, and then seconds. The first set of numbers can be identified as degrees thanks to the degree symbol.
Latitude and longitude are a pair of numbers (coordinates) used to describe a position on the plane of a geographic coordinate system. The numbers are in decimal degrees format and range from -90 to 90 for latitude and -180 to 180 for longitude.
I can't see any point in having an interface for this, to be honest.
I would just create a struct, but make it immutable - mutable structs are a really bad idea. I'd also use full Latitude
and Longitude
as the property names. Something like this:
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); } }
I'd then also implement IEquatable<GeoCoordinate>
and override Equals
and GetHashCode
, e.g.
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(); }
Note that you need to be aware of the normal dangers of performing equality comparisons on doubles - there's not much alternative here, but two values which look like they should be equal may not be...
The point about the parameterless constructor is a reasonable one, but I suspect you'll find it won't actually bite you.
Make it a struct, for performance.
Note that the fact that you can't have a constructor is completely negated by the C# 3.5+ intializer syntax:
new SLatLng { Lat = 1.0, Lng = 2.0 }
Note that adding the interface inevitably reduces performance: interfaces cannot define fields, a struct without fields is hardly useful. That leaves only one realistic scenario: the interface requires you to define the properies to access fields.
If you are obliged to use the properties (via getter/setter) you will loose performance of direct access. Compare:
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; } }
Generate setter invocation and boxing
.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; } }
Generates direct assignment and (obviously) no boxing
// 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With