web-dev-qa-db-ja.com

Lat / Lng座標を表すには、構造体またはクラスを使用する必要がありますか?

私はジオコーディングAPIを使用しており、返されるポイントの座標を緯度/経度のペアとして表す必要があります。ただし、構造体とクラスのどちらを使用するかはわかりません。私の最初の考えは構造体を使用することでしたが、それらは一般的にC#で眉をひそめているようです(たとえば、Jon Skeetはこの回答で と言及しています 「Iほとんどカスタム構造体を定義することはありません ")。パフォーマンスとメモリ使用量は、アプリケーションでは重要な要素ではありません。

これまでのところ、シンプルなインターフェースに基づいてこれらの2つの実装を考え出しました。

インターフェース

_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},{1}", this.Lat, this.Lng);
    }
}
_

いくつかのテストを実行したところ、次のことがわかりました。

  • 構造体には常にパラメーターのないコンストラクターがあります。つまり、クラスの場合のように、2つのプロパティ(latとlng)を期待するコンストラクターでインスタンス化するように強制することはできません。

  • 構造体(値型)がnullになることはないため、常に値が含まれます。しかし、インターフェースを実装すれば、次のようなことができます。

    ILatLng s = new SLatLng(); s = null;

では、この場合、構造体がインターフェースを使用することは理にかなっていますか?

  • 構造体を使用する場合、EqualsGetHashCode()などをオーバーライドする必要がありますか?私のテストは、(クラスとは異なり)比較が正しく機能しないことを示しています-必要ですか?

  • クラスを使用するとより「快適」に感じるので、クラスがどのように動作するかを知っているので、クラスに固執するのが最善ですか?私のコードを使用している人々は、特にインターフェイスで作業しているときに、値型のセマンティクスによって混乱するでしょうか?

  • CLatLngの実装では、GetHashCode()のオーバーライドは問題ないように見えますか?私はこの記事からそれを「盗んだ」ので、わからない!

どんな助けやアドバイスもありがたく受けました!

38
Dan Diplo

正直に言うと、これのためのインターフェースを持つことに意味がありません。

私は単に構造体を作成しますが、それを不変にします-可変構造体は本当に悪い考えです。プロパティ名として完全なLatitudeLongitudeも使用します。このようなもの:

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);
    }
}

私はそれからalso実装IEquatable<GeoCoordinate>およびEqualsGetHashCodeをオーバーライドします。

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();
}

あなたはダブルで等値比較を実行することの通常の危険性に注意する必要があることに注意してください-ここに多くの選択肢はありませんが、lookであるはずの2つの値は等しいはずです...

パラメーターなしのコンストラクターについての要点は妥当なものですが、あなたはそれが実際にはに噛まれないことに気付くと思います。

57
Jon Skeet

パフォーマンスのために、それを構造体にしてください。

  • パフォーマンスのメリットは、たとえば、これらの構造体の配列。たとえば、 System.Collections.Generic.Listは、.Net配列内の要素タイプのボックス化されていないストレージを正しく処理するため、ジェネリックコンテナーにも同様に適用されます。
  • コンストラクターを使用できないという事実は、C#3.5+の初期化構文によって完全に無効化されていることに注意してください。

    new SLatLng { Lat = 1.0, Lng = 2.0 }
    

インターフェース使用のコスト

インターフェースを追加すると、必然的にパフォーマンスが低下することに注意してください。インターフェースはフィールドを定義できません。フィールドのない構造体はほとんど役に立ちません。これにより、現実的なシナリオが1つだけ残ります。インターフェイスでは、フィールドにアクセスするためのプロパティを定義する必要があります。

(ゲッター/セッターを介して)プロパティを使用する義務がある場合、直接アクセスのパフォーマンスが低下します。比較:

インターフェース付き

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
9
sehe

構造体と値型は.netオブジェクト階層の外にあるエンティティですが、構造体を定義するたびに、システムは主に構造体のように動作するValueTypeから派生した疑似クラスも定義します。拡大変換演算子は、構造体と疑似クラスの間に定義されます。インターフェース型であると宣言された変数、パラメーター、フィールドは常にクラスオブジェクトとして処理されることに注意してください。何かがかなりの程度までインターフェースとして使用される場合、多くの場合、それはクラスである可能性があります。

一部の人々は、可変構造体の悪を警戒していますが、値の意味を持つ可変構造体が非常に役立つ場所はたくさんあります システムの処理の欠陥のためではなかったのか。例えば:

  1. 「自己」を変更するメソッドとプロパティは、読み取り専用コンテキストでのアプリケーションを禁止する属性でタグ付けする必要があります。このような属性のないメソッドは、互換性スイッチでコンパイルしない限り、「自己」の変更を禁止する必要があります。
  2. MyDictOfPoints( "George")。X ++;のようなものを容易にするために、特定の式を裏返しにするか、標準タイプのproperty-delegate-pairを持つことによって、構造体またはそのフィールドへの参照を渡す手段が必要です。

多くの場合、値型セマティックスは、一部の一般的な言語で前者があまりサポートされていないという事実だけでなく、参照セマンティクスよりもはるかに「期待」されます。

PS--私は、可変構造はしばしば適切で適切なものですが、「自己」を変異させる構造メンバーは扱いが不適切であり、回避する必要があることをお勧めします。新しい構造を返す関数(たとえば、「AfterMoving(Double distanceMiles、Double headingDegrees)」)を使用する新しいLatLongを返す関数を使用するか、指定された距離を移動した後の位置であるLatLong)、または静的メソッド(たとえば、「MoveDistance」を使用します。 (参照LatLong位置、倍の距離マイル、倍の見出し度) ")。可変構造体は一般的に、それらが基本的に変数のグループを表す場所で使用されるべきです。

4
supercat

あなたは変更可能な構造体を作ろうとしている、それはノーノーです。特にインターフェースを実装しているので!

LatLngタイプを変更可能にする場合は、参照タイプを使用します。それ以外の場合、この特定の例ではstructで問題ありません。

3
Dyppl

構造体を使用します。クラスは、ここでの単純な使用にとってはやり過ぎです。例として、Pointなどの他の構造体を見ることができます。

3
Puppy

あなたの場合、インターフェースは必要ありません。単純な古いstructにしてください。これにより、インターフェースを介してstructを渡すときに不要なボクシングが行われなくなります。

2

codeplexのこの座標ライブラリ によって提供される正当化とガイダンスが本当に好きです。その中で、クラスを使用し、latとlongの実際の値を表すためにfloatを使用しています。

1
Irwin

個人的には、LatLongのような単純なクラスであっても、クラスを使用することを好みます。私のc ++日以来、私は構造体を使用していません。クラスの追加の利点は、より複雑な機能が必要な場合に将来的にクラスを拡張できることです。

私はこのスレッドの他の人々と同意しますが、インターフェイスではやり過ぎのように見えますが、アプリケーションでこのオブジェクトを使用するために必要なものについて完全なコンテキストがないので。

最後に、GetHashCodeは「Lat * Long」を実行するための美化された方法のようです。それが安全な賭けであるかどうかはわかりません。また、アプリケーションでGetHashCodeを何度も使用することを計画している場合は、メソッドのパフォーマンスを改善するためにそれを単純に保つことをお勧めします。

0
Sharath Satish