25

以下は、クラスがC#の構造体と異なる唯一の方法です(間違っている場合は訂正してください)。

  • クラス変数は参照ですが、構造体変数は値であるため、構造体の値全体が割り当てとパラメーターパスにコピーされます
  • クラス変数はスタックに格納されたポインタであり、ヒープ上のメモリを指しますが、構造体変数は格納されたヒープに値として格納されます

不変の構造体、つまり、一度初期化すると変更できないフィールドを持つ構造体があるとします。この構造体をパラメーターとして渡すか、割り当てで使用するたびに、値がコピーされてスタックに格納されます。

次に、この不変の構造体を不変のクラスにするとします。このクラスの単一インスタンスは一度作成され、クラスへの参照のみが割り当てとパラメーターパスでコピーされます。

オブジェクトが変更可能である場合、これら2つの場合の動作は異なります。オブジェクトを変更すると、最初のケースでは構造体のコピーが変更され、2番目のケースでは元のオブジェクトが変更されます。ただし、どちらの場合もオブジェクトは不変であるため、これが実際にこのオブジェクトのユーザーのクラスであるか構造体であるかには違いはありません。

参照のコピーは構造体のコピーよりも安価なので、なぜ不変の構造体を使用するのでしょうか。

また、可変構造体は悪であるため、構造体を使用する理由はまったくないようです。

私はどこが間違っていますか?

4

4 に答える 4

31

参照のコピーは構造体のコピーよりもコストがかからないのに、なぜ不変の構造体を使用するのでしょうか?

これは常に正しいとは限りません。参照のコピーは、64 ビット OS では 8 バイトになり、多くの構造体よりも大きくなる可能性があります。

また、クラスの作成にはコストがかかる可能性が高いことに注意してください。多くの場合、構造体の作成は完全にスタック上で行われますが (多くの例外があります)、これは非常に高速です。クラスを作成するには、オブジェクト ハンドル (ガベージ コレクター用) を作成し、スタックに参照を作成し、オブジェクトの有効期間を追跡する必要があります。これにより、GC の負荷が増大する可能性があり、これにも実際のコストがかかります。

そうは言っても、大規模な不変構造体を作成することはおそらく良い考えではありません。これは、構造体が 16 バイトを超える場合、またはボックス化される場合、クラスと構造体を選択するためのガイドラインが常にクラスを使用することを推奨している理由の一部です。違いを小さくするその他の問題。

そうは言っても、私は多くの場合、問題の型の使用目的と意味に基づいて決定を下します。値型は、単一の値を参照するために使用する必要があり (ガイドラインを参照してください)、多くの場合、クラスとは異なるセマンティックな意味と予想される使用法を持ちます。これは、多くの場合、クラスまたは構造体を選択する際のパフォーマンス特性と同じくらい重要です。

于 2013-01-03T21:17:56.603 に答える
26

リードの答えは非常に優れていますが、いくつかの点を追加するだけです:

私が間違っている場合は修正してください

あなたは基本的にここで正しい軌道に乗っています。変数を混同するというよくある間違いを犯しました。変数は保存場所です。値は変数に格納されます。そして、あなたは「値型がスタックに入る」という一般的に言われている神話をいじっています。むしろ、変数はストレージの場所であるため、変数は短期ストレージまたは長期ストレージのいずれかに移動します。変数が短期保存されるか長期保存されるかは、ではなく既知の有効期間に依存します。

しかし、そのすべてがあなたの質問に特に関連しているわけではなく、要約すると、この三段論法の反駁を求めることになります。

  • 可変構造体は悪です。
  • 参照のコピーは構造体のコピーよりもコストがかからないため、不変の構造体は常に劣っています。
  • したがって、構造体を使用することは決してありません。

いくつかの方法で三段論法に異議を唱えることができます。

まず、はい、変更可能な構造体は悪です。ただし、一部の限られたシナリオではパフォーマンス上の利点が得られるため、非常に役立つ場合があります。他の合理的な手段が尽きて、実際のパフォーマンスの問題がない限り、このアプローチはお勧めしません。

第 2 に、参照のコピーは必ずしも構造体のコピーよりも安価であるとは限りません。参照は通常、4 または 8 バイトのマネージ ポインターとして実装されます (ただし、これは実装の詳細です。不透明なハンドルとして実装することもできます)。参照サイズの構造体をコピーすることは、参照サイズの参照をコピーすることよりも安くも高くもありません。

第 3 に、参照のコピーが構造体のコピーよりもコストがかからない場合でも、フィールドを取得するには参照を逆参照する必要があります。逆参照はゼロコストではありません! 参照を逆参照するのにマシン サイクルがかかるだけでなく、そうするとプロセッサ キャッシュが台無しになる可能性があり、将来の逆参照がはるかに高価になる可能性があります。

第 4 に、たとえ参照のコピーが構造体のコピーより安価だとしても、誰が気にしますか? それが許容できないパフォーマンス コストを生み出しているボトルネックでない場合、どちらが高速かはまったく関係ありません。

第 5 に、参照はメモリ空間で構造体よりもはるかにコストがかかります。

第 6 に、ガベージ コレクタによって参照のネットワークを定期的に追跡する必要があるため、参照によってコストが追加されます。「blittable」構造体は、ガベージ コレクターによって完全に無視される場合があります。ガベージコレクションは多額の費用がかかります。

第 7 に、参照型とは異なり、不変の値型を null にすることはできません。すべての値が適切な値であることを知っています。Reed が指摘したように、参照型の適切な値を取得するには、 allocator と constructor の両方を実行する必要があります。それは安くはありません。

8番目に、値型は値を表し、プログラムは多くの場合、値の操作に関するものです。どちらが「安い」かに関係なく、「値」と「参照」の両方の比喩を言語に「焼き込む」ことは理にかなっています。

于 2013-01-04T06:13:51.183 に答える
2

MSDNから;

クラスは参照型で、構造体は値型です。参照型はヒープに割り当てられ、メモリ管理はガベージ コレクターによって処理されます。値型はスタックまたはインラインに割り当てられ、スコープ外になると割り当てが解除されます。一般に、値型は割り当てと割り当て解除が安価です。ただし、大量のボックス化とボックス化解除を必要とするシナリオで使用すると、参照型と比較してパフォーマンスが低下します。

型が次の特性をすべて備えていない限り、構造体を定義しないでください。

  • プリミティブ型 (integer、double など) と同様に、単一の値を論理的に表します。

  • インスタンス サイズが 16 バイト未満です。

  • 不変です。

  • 頻繁に箱詰めする必要はありません。

したがって、構造体が 16 バイトを超える場合は、常に構造体の代わりにクラスを使用する必要があります。http://www.dotnetperls.com/structからもお読みください

于 2013-01-03T21:20:50.017 に答える
1

構造体には 2 つの使用例があります。不透明な構造は、不変のクラスを使用して実装できるものに役立ちますが、十分に小さいため、最高の状況であっても、クラスを使用するメリットはほとんどありません。作成されて破棄される頻度は、単純にコピーされる頻度のかなりの割合です。たとえば、Decimalは 16 バイトの構造体であるため、100 万個のDecimal値を保持するには 16 メガバイトが必要になります。クラスの場合、Decimalインスタンスへの各参照には 4 または 8 バイトが必要ですが、個別のインスタンスごとにさらに 20 ~ 32 バイトが必要になるでしょう。要素が少数の異なる配列からコピーされた多くの大きな配列がある場合DecimalDecimalインスタンス、クラスが勝つ可能性がありますが、ほとんどのシナリオでは、構造体が勝つことを意味するの 100 万の異なるインスタンスへの 100 万の参照を持つ配列を持つ可能性が高くなります。

このように構造体を使用することは、通常、MSDN から引用されたガイドラインが適用される場合にのみ有効です (ただし、不変性のガイドラインは主に、構造体メソッドが基になる構造体を変更することを示す方法がまだないという事実の結果です)。最後の 3 つのガイドラインのいずれにも当てはまらない場合は、構造体よりも不変クラスを使用する方がよいでしょう。ただし、最初のガイドラインが適用されない場合、不透明な構造体を使用すべきではなく、代わりにクラスを使用すべきではないことを意味します。

場合によっては、データ型の目的は、変数のグループをダクトテープで固定して、それらの値を 1 つの単位として渡すことができるようにすることですが、意味的には別個の変数として残ります。たとえば、多くのメソッドでは、3 次元座標を表す 3 つの浮動小数点数のグループを渡す必要がある場合があります。Point3d三角形を描画したい場合、9 つの浮動小数点数よりも3 つのパラメーターを渡す方がはるかに便利です。多くの場合、そのような型の目的は、ドメイン固有の動作を与えることではなく、単純に物事を便利に渡す手段を提供することです。このような場合、構造体を適切に使用すれば、構造体はクラスよりも大きなパフォーマンス上の利点を提供できます。型の 3 つの変数を表す構造体doubleダクト テープで固定された には、 type の 3 つのパブリック フィールドが必要ですdouble。このような構造体により、2 つの一般的な操作を効率的に実行できます。

  1. 与えられたインスタンスで、その状態のスナップショットを作成して、スナップショットを乱すことなくインスタンスを変更できるようにします
  2. 不要になったインスタンスを考えると、どうにかしてわずかに異なるインスタンスを考え出す

不変のクラス型を使用すると、クラスが保持するデータ量に関係なく、最初のクラスを固定コストで実行できますが、2 番目のクラスは非効率的です。変数が表すと想定されるデータの量が多いほど、最初の操作を実行するときの構造体に対する不変クラス型の利点が大きくなり、2 番目の操作を実行するときの露出フィールド構造体の利点が大きくなります。

可変クラス型は、2 番目の操作が支配的で、最初の操作がほとんど必要ないシナリオでは効率的ですが、オブジェクト自体を外部の変更にさらすことなく、オブジェクトが可変クラス オブジェクトの現在の値を公開することは困難な場合があります。

使用パターンによっては、大きな公開フィールド構造が、不透明な構造やクラス型よりもはるかに効率的であることに注意してください。17 バイトを超える構造は、小さい構造よりも効率が悪いことがよくありますが、それでもクラスよりははるかに効率的です。さらに、構造体をrefパラメーターとして渡すコストは、そのサイズに依存しません。大きな構造体は、フィールドではなくプロパティを介してアクセスしたり、不必要に値で渡したりする場合は非効率的ですが、冗長な「コピー」操作を避けるように注意すると、クラスと構造体の損益分岐点がない使用パターンがあります。構造体 - 構造体は単純にパフォーマンスが向上します。

一部の人々は、型がフィールドを公開しているという考えに恐怖を覚えるかもしれませんが、私が説明するような構造体は、それ自体がエンティティとしてではなく、読み取るものの拡張と考えるべきであることをお勧めします。またはそれを書きます。例えば:

public struct SlopeAndIntercept
{
   public double Slope,Intercept;
}
public SlopeAndIntercept FindLeastSquaresFit() ...

多数のポイントの最小二乗フィットを実行するコードは、結果のラインの勾配または Y 切片を見つけるためにかなりの量の作業を行う必要があります。両方を見つけても、それ以上の費用はかかりません。メソッドを呼び出すコードはFindLeastSquaresFit、1 つの変数に勾配を持ち、別の変数に切片を持ちたいと思うでしょう。そのようなコードの場合:

var resultLine = FindLeastSquaresFit();

その結果、2 つの変数が効果的に作成さresultLine.SloperesultLine.Intercept、メソッドが適切と思われるように操作できます。のフィールドは実際にはにも にもresultLine属していません。を宣言するコードに属します。メソッドが次のように使用された場合と状況は少し異なります。SlopeInterceptFindLeastSquaresFitresultLine

double Slope, Intercept;
FindLeastSquaresFit(out Slope, out Intercept);

そのコンテキストでは、関数呼び出しの直後に、2 つの変数にメソッドによって割り当てられた意味があることは明らかですが、それ以外の時点での意味は、メソッドがそれらに対して他に何を行うかによって異なります。前述の構造体のフィールドについても同様です。

透過的な構造ではなく、不変クラスを使用してデータを返す方がよい場合があります。とりわけ、クラスを使用すると、 を返す関数の将来のバージョンで、Foo追加情報を含む何かを返すことが容易になります。一方、コードが個別のものの特定のセットを処理することを期待する状況が多くあり、そのセットを変更すると、クライアントがそれを処理する必要が根本的に変わります。たとえば、(x,y) ポイントを処理するコードがたくさんある場合、「z」座標を追加するにはそのコードを書き直す必要があり、「ポイント」タイプがそれを緩和するためにできることは何もありません。

于 2013-01-04T01:01:18.520 に答える