How do you choose between implementing a value object (the canonical example being an address) as an immutable object or a struct?
Are there performance, semantic or any other benefits of choosing one over the other?
How do you choose between implementing a value object (the canonical example being an address) as an immutable object or a struct?
Are there performance, semantic or any other benefits of choosing one over the other?
考慮すべき点がいくつかあります。
構造体がスタックに割り当てられます (通常)。これは値型であるため、メソッド間でデータを渡すと、大きすぎるとコストがかかる可能性があります。
クラスはヒープ上に割り当てられます。これは参照型であるため、メソッドを介してオブジェクトを渡すことはそれほどコストがかかりません。
通常、あまり大きくない不変オブジェクトには構造体を使用します。それらに保持されるデータの量が限られている場合、または不変性が必要な場合にのみ使用します。例はDateTime
構造体です。オブジェクトが のようなものほど軽量でない場合、DateTime
おそらく構造体として使用する価値はないと考えるのが好きです。また、オブジェクトが値型として渡されても意味がない場合 (同様にDateTime
)、構造体として使用するのは役に立たない可能性があります。ただし、ここでは不変性が重要です。また、構造体はデフォルトでは不変ではないことを強調したいと思います。設計上、それらを不変にする必要があります。
私が遭遇する 99% の状況では、クラスを使用するのが適切です。不変クラスはあまり必要ないことがよくあります。ほとんどの場合、クラスは変更可能であると考える方が自然です。
値オブジェクト (標準的な例はアドレス) を不変オブジェクトとして実装するか、構造体として実装するかをどのように選択しますか?
あなたの選択肢は間違っていると思います。不変オブジェクトと構造体は相反するものではなく、唯一の選択肢でもありません。代わりに、次の 4 つのオプションがあります。
.NET では、ロジックを表す可変クラスとエンティティを表す不変クラスをデフォルトで選択する必要があると私は主張します。私は実際には、ロジックの実装であっても、可能な場合でも不変のクラスを選択する傾向があります。構造体は、値のセマンティクスをエミュレートする小さな型 (カスタム型、数値型の類似エンティティなど) 用に予約する必要があります。データの大きなブロブをコピーしたくないので、ここでは小さいことに重点を置いています。参照による間接化は実際には安価です (したがって、構造体を使用してもあまり得られません)。私はいつも構造体を作る傾向がありますDate
Complex
不変(現時点では単一の例外は考えられません)。これは本質的な値の型のセマンティクスに最も適しているため、従うのが良いルールだと思います。
私は思考実験を使うのが好きです:
空のコンストラクターのみが呼び出された場合、このオブジェクトは意味がありますか?
Richard Eのリクエストで編集
の良い使い方はstruct
、プリミティブをラップし、それらを有効な範囲にスコープすることです。
たとえば、確率の有効な範囲は 0 ~ 1 です。これを表すために小数を使用すると、エラーが発生しやすくなり、使用するたびに検証が必要になります。
代わりに、プリミティブを検証やその他の便利な操作でラップできます。ほとんどのプリミティブは自然な 0 状態を持っているため、これは思考実験に合格します。
struct
確率を表すの使用例を次に示します。
public struct Probability : IEquatable<Probability>, IComparable<Probability>
{
public static bool operator ==(Probability x, Probability y)
{
return x.Equals(y);
}
public static bool operator !=(Probability x, Probability y)
{
return !(x == y);
}
public static bool operator >(Probability x, Probability y)
{
return x.CompareTo(y) > 0;
}
public static bool operator <(Probability x, Probability y)
{
return x.CompareTo(y) < 0;
}
public static Probability operator +(Probability x, Probability y)
{
return new Probability(x._value + y._value);
}
public static Probability operator -(Probability x, Probability y)
{
return new Probability(x._value - y._value);
}
private decimal _value;
public Probability(decimal value) : this()
{
if(value < 0 || value > 1)
{
throw new ArgumentOutOfRangeException("value");
}
_value = value;
}
public override bool Equals(object obj)
{
return obj is Probability && Equals((Probability) obj);
}
public override int GetHashCode()
{
return _value.GetHashCode();
}
public override string ToString()
{
return (_value * 100).ToString() + "%";
}
public bool Equals(Probability other)
{
return other._value.Equals(_value);
}
public int CompareTo(Probability other)
{
return _value.CompareTo(other._value);
}
public decimal ToDouble()
{
return _value;
}
public decimal WeightOutcome(double outcome)
{
return _value * outcome;
}
}
要因:構造、メモリ要件、ボクシング。
通常、構造体のコンストラクター制限(明示的なパラメーターなしのbase
コンストラクター、構造体なし)は、構造体を使用するかどうかを決定します。たとえば、パラメーターなしのコンストラクターがメンバーをデフォルト値に初期化する必要がない場合は、不変オブジェクトを使用します。
それでも2つの選択肢がある場合は、メモリ要件を決定します。特に多くのインスタンスが予想される場合は、小さなアイテムを構造体に格納する必要があります。
インスタンスがボックス化されると(たとえば、匿名関数用にキャプチャされたり、非ジェネリックコンテナに格納されたりすると)、その利点は失われます。ボックス化に追加料金を支払うこともできます。
「小さい」とは何ですか、「多い」とは何ですか?
オブジェクトのオーバーヘッドは、32ビットシステムでは(IIRC)8バイトです。数百のインスタンスでは、これにより、内部ループがキャッシュ内で完全に実行されるかどうか、またはGCを呼び出すかどうかがすでに決定されている可能性があることに注意してください。数万のインスタンスが予想される場合、これが実行とクロールの違いである可能性があります。
そのPOVから、構造体の使用は時期尚早の最適化ではありません。
したがって、経験則として:
ほとんどのインスタンスがボックス化される場合は、不変オブジェクトを使用してください。それ以外の場合、小さなオブジェクトの場合、構造体の構築によってインターフェイスが扱いにくくなり、インスタンスが数千を超えないと
予想
される場合にのみ、不変オブジェクトを使用してください。
実際、値オブジェクトの実装に .NET 構造体を使用することはお勧めしません。2 つの理由があります。
ここで、このトピックについて詳しく説明します:値オブジェクトの説明
一般的に、ビジネスオブジェクトの構造体はお勧めしません。この方向に進むことでパフォーマンスが少し向上する可能性がありますが、スタックで実行していると、何らかの方法で自分自身を制限することになり、場合によってはデフォルトのコンストラクターが問題になる可能性があります。
一般にリリースされているソフトウェアがある場合、これはさらに不可欠であると私は言います。
構造体は単純な型には問題ありません。そのため、Microsoftはほとんどのデータ型に構造体を使用しています。同様に、構造体はスタック上で意味のあるオブジェクトに適しています。回答の1つで言及されているPoint構造体は、良い例です。
どうすればいいですか?私は通常、デフォルトでオブジェクトを使用します。構造体であることがメリットになると思われる場合は、構造体として実装された単純な型のみを含むかなり単純なオブジェクトであると思われる場合は、それを検討して、それが実現するかどうかを判断します。検出。
例として住所について言及します。1つをクラスとして調べてみましょう。
public class Address
{
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
}
このオブジェクトを構造体として考えてください。このようにコーディングした場合は、このアドレス「struct」内に含まれるタイプを考慮してください。あなたはあなたが望むようにうまくいかないかもしれない何かを見ますか?潜在的なパフォーマンス上の利点を考慮してください(つまり、1つあります)?
今日の世界 (私は C# 3.5 を考えています) では、構造体の必要性はわかりません (編集: いくつかのニッチなシナリオを除いて)。
pro-struct の議論は、主に認識されたパフォーマンス上の利点に基づいているようです。これを説明するベンチマーク (実際のシナリオを再現するもの) をいくつか見てみたいと思います。
「軽量」のデータ構造に構造体を使用するという概念は、私の好みには主観的すぎるようです。データが軽量でなくなるのはいつですか? また、構造体を使用するコードに機能を追加する場合、いつその型をクラスに変更することにしますか?
個人的には、最後に C# で構造体を使用したのはいつか思い出せません。
パフォーマンス上の理由から C# で構造体を使用することは、時期尚早な最適化の明確なケースであることをお勧めします*
* アプリケーションのパフォーマンスがプロファイリングされていて、クラスの使用がパフォーマンスのボトルネックとして特定されている場合を除く
MSDNの状態:
構造体型は、Point、Rectangle、Color などの軽量オブジェクトを表すのに適しています。ポイントをクラスとして表現することは可能ですが、一部のシナリオでは構造体の方が効率的です。たとえば、1000 個の Point オブジェクトの配列を宣言すると、各オブジェクトを参照するために追加のメモリが割り当てられます。この場合、構造体の方が安価です。
参照型のセマンティクスが必要でない限り、16 バイトより小さいクラスは、システムによって構造体としてより効率的に処理される場合があります。
オブジェクト モデリングの観点から、私は構造体を高く評価しています。構造体を使用すると、コンパイラを使用して特定のパラメーターとフィールドを null 非許容として宣言できるからです。もちろん、特別なコンストラクタ セマンティクス ( Spec#など) がなければ、これは自然な「ゼロ」値を持つ型にのみ適用されます。(したがって、Bryan Watt の「ただし実験」の回答です。)
経験則として、構造体のサイズは 16 バイトを超えてはなりません。それ以外の場合、メソッド間で渡すと、(32 ビット マシンで) わずか 4 バイトの長さのオブジェクト参照を渡すよりもコストが高くなる可能性があります。
もう 1 つの懸念事項は、デフォルトのコンストラクターです。構造体には常にデフォルトの (パラメーターなしでパブリックな) コンストラクターがあり、それ以外の場合は次のようなステートメントがあります
T[] array = new T[10]; // array with 10 values
動作しません。
==
さらに、構造体が演算子と演算子を上書きし、インターフェイス!=
を実装することは礼儀正しいです。IEquatable<T>
値渡しの場合、インスタンスをコピーするコストはいくらですか。
高い場合は不変の参照 (クラス) 型、それ以外の場合は値 (構造体) 型。
構造体は、( out および ref とともに) 上級者向けです。
はい、構造体はrefを使用すると優れたパフォーマンスを発揮しますが、使用しているメモリを確認する必要があります。誰が記憶などを支配するのか
構造体で ref と outs を使用していない場合、厄介なバグが予想される場合は価値がありません:-)