c# - geocoordinate - ¿Debo usar una estructura o una clase para representar una coordenada Lat/Lng?
c# geolocation (8)
Estoy trabajando con una API de geocodificación y necesito representar las coordenadas de un punto devuelto como un par de Latitud / Longitud. Sin embargo, no estoy seguro si usar una estructura o una clase para esto. Mi idea inicial fue utilizar una estructura, pero en general parecen desaprobar en C # (por ejemplo, Jon Skeet menciona en esta respuesta que " casi nunca defino estructuras personalizadas"). El rendimiento y el uso de memoria no son factores críticos en la aplicación.
Hasta ahora he llegado a estas dos implementaciones basadas en una interfaz simple:
Interfaz
public interface ILatLng
{
double Lat { get; }
double Lng { get; }
}
Implementación de clase 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));
}
}
Implementación LatLng Struct
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);
}
}
Al realizar algunas pruebas, he llegado a los siguientes hallazgos:
Una estructura siempre tiene un constructor sin parámetros, lo que significa que no se puede obligar a crear una instancia con un constructor que espera dos propiedades (para lat y lng), como se puede hacer con una clase.
Una estructura (que es un tipo de valor) nunca puede ser nula, por lo que siempre contendrá un valor. Pero aún puede hacer cosas como esta si implementa una interfaz:
ILatLng s = new SLatLng(); s = null;
Entonces, ¿tiene sentido que una estructura use una interfaz en este caso?
Si utilizo una estructura ¿debo anular
Equals
,GetHashCode()
etc.? Mis pruebas indican que las comparaciones funcionan correctamente sin hacerlo (a diferencia de una clase), ¿es necesario?Me siento más "cómodo" al usar las clases, así que ¿es mejor simplemente seguir con ellos ya que soy más consciente de cómo se comportan? ¿Se confundirá a las personas que usan mi código por semántica de tipo de valor, especialmente cuando se trabaja en una interfaz?
En la implementación
CLatLng
, ¿la anulación deGetHashCode()
parece correcta? Lo ''robé'' de este artículo, ¡así que no estoy seguro!
¡Cualquier ayuda o consejo recibido con gratitud!
Estás tratando de hacer una estructura mutable, eso es un no-no. ¡Especialmente desde que implementa una interfaz!
Si desea que su tipo LatLng
sea mutable, LatLng
con el tipo de referencia. De lo contrario, struct está bien para este ejemplo en particular.
Hazlo una estructura para el rendimiento.
- El beneficio de rendimiento se multiplicará muchas veces cuando maneje, por ejemplo, arreglos de estas estructuras. Tenga en cuenta que, por ejemplo, System.Collections.Generic.List maneja correctamente el almacenamiento no empaquetado del tipo de elemento en matrices .Net, por lo que se aplica también a los contenedores genéricos.
Tenga en cuenta que el hecho de que no se pueda tener un constructor está completamente anulado por la sintaxis del intializador C # 3.5+:
new SLatLng { Lat = 1.0, Lng = 2.0 }
Costo de uso de la interfaz
Tenga en cuenta que agregar la interfaz inevitablemente reduce el rendimiento: las interfaces no pueden definir campos, una estructura sin campos no es útil. Eso deja solo un escenario realista: la interfaz requiere que definas las propiedades para acceder a los campos.
Si está obligado a utilizar las propiedades (a través de getter / setter) perderá rendimiento de acceso directo. Comparar:
Con interfaz
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;
}
}
Generar invocación de setter y boxeo
.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
Sin interfaz
public class Y
{
struct Test
{
public int x;
}
public static void Main(string[] ss)
{
var t = new Test { x=42 };
Test copy = t;
}
}
Genera asignación directa y (obviamente) no boxeo
// 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
Las estructuras y los tipos de valores son entidades que se encuentran fuera de la jerarquía de Objetos .net, pero cada vez que se define una estructura, el sistema también define una pseudoclase derivada de ValueType que se comporta en gran medida como la estructura; los operadores de conversión de ampliación se definen entre la estructura y la pseudoclase. Tenga en cuenta que las variables, parámetros y campos que se declaran de tipo interfaz siempre se procesan como objetos de clase. Si se va a utilizar algo como una interfaz en algún grado significativo, en muchos casos también puede ser una clase.
Aunque algunas personas critican los males de las estructuras mutables, hay muchos lugares donde las estructuras mutables con semántica de valores serían muy útiles si no fuera por las deficiencias en el manejo de las mismas por parte del sistema . Por ejemplo:
- Los métodos y las propiedades que cambian "self" deberían etiquetarse con un atributo que prohibiría su aplicación en contextos de solo lectura; los métodos sin tal atributo deberían estar prohibidos de mutar "uno mismo" a menos que se compile con un conmutador de compatibilidad.
- Debería haber medios para pasar referencias a estructuras o campos de los mismos girando ciertas expresiones de adentro hacia afuera, o teniendo un tipo estándar de propiedad-delegate-pair, para facilitar cosas como myDictOfPoints ("George"). X ++;
A menudo, las sematics de tipo de valor serían mucho más "esperadas" que la semántica de referencia, si no fuera solo por el hecho de que las primeras son tan poco compatibles en algunos lenguajes comunes.
PD: Sugeriría que, aunque las estructuras mutables son a menudo una buena y apropiada, los miembros de la estructura que mutan a "sí mismos" se manejan mal y deben evitarse. Utilice funciones que devolverán una nueva estructura (por ejemplo, "AfterMoving (Double distanceMiles, Double headingDegrees)") que devolvería un nuevo LatLong cuya posición era la que estaría después de mover una distancia especificada, o use métodos estáticos (por ejemplo, "MoveDistance"). (ref. Posición de LatLong, Double distanceMiles, Double headingDegrees) "). Las estructuras mutables generalmente deben usarse en lugares donde representan esencialmente un grupo de variables.
Me gusta mucho la justificación y la orientación proporcionadas por esta biblioteca de coordenadas en codeplex. En ella, usan clases y para representar los valores reales para lat y long usan float.
No necesita una interfaz en su caso. Solo haz que sea una vieja struct
simple. Esto evitará cualquier boxeo innecesario al pasar la struct
través de su interfaz.
No puedo ver ningún punto al tener una interfaz para esto, para ser honesto.
Simplemente crearía una estructura, pero la haría inmutable: las estructuras mutables son una muy mala idea. También usaría Latitude
y Longitude
como nombres de propiedad. Algo como esto:
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);
}
}
También implementaría IEquatable<GeoCoordinate>
y anularía Equals
y GetHashCode
, por ejemplo
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();
}
Tenga en cuenta que debe tener en cuenta los peligros normales de realizar comparaciones de igualdad en dobles: no hay muchas alternativas aquí, pero dos valores que parecen iguales deberían no ser ...
El punto sobre el constructor sin parámetros es razonable, pero sospecho que encontrarás que en realidad no te morderá.
Personalmente, prefiero usar clases incluso si son simples, como tu LatLong. No he usado estructuras desde mis días de c ++. La ventaja adicional de las clases es la posibilidad en el futuro de ampliarlas si se requiere una funcionalidad más compleja.
Estoy de acuerdo con las otras personas en este hilo de que una interfaz parece una exageración, ya que no tengo un contexto completo de lo que está utilizando este objeto porque puede ser necesario en su aplicación.
Por último, su GetHashCode parece ser una forma glorificada de hacer "Lat * Long". No estoy seguro de si es una apuesta segura. Además, si planea usar GetHashCode muchas veces en su aplicación, le recomendaría que sea simple para mejorar el rendimiento del método.
Yo usaría una estructura. Una clase es demasiado exagerada para el uso simple aquí; puedes mirar otras estructuras como Point para ver un ejemplo.