C#ではクラスではなく構造体をいつ使用すればよいですか?


1391

C#でクラスではなく構造体を使用する必要があるのはいつですか?私の概念モデルでは、構造体は、項目が単なる値型のコレクションであるときに使用されます。それらを論理的にまとめて、まとまりのある全体にする方法。

ここでこれらのルールに遭遇しました

  • 構造体は単一の値を表す必要があります。
  • 構造体のメモリフットプリントは16バイト未満である必要があります。
  • 構造体は作成後に変更しないでください。

これらのルールは機能しますか?構造体は意味的にどういう意味ですか?


248
System.Drawing.Rectangleこれら3つのルールすべてに違反しています。
ChrisW、2009

4
C#で記述された商用ゲームはかなり多くありますが、ポイントは、最適化されたコードに使用されるということです
BlackTigerX

25
構造体は、一緒にグループ化する値タイプの小さなコレクションがある場合に、より良いパフォーマンスを提供します。これはゲームプログラミングで常に発生します。たとえば、3Dモデルの頂点には、位置、テクスチャ座標、および法線があり、一般的に不変になることもあります。単一のモデルには数千の頂点がある場合もあれば、1ダースある場合もありますが、この使用シナリオでは、構造体のオーバーヘッドが全体的に少なくなります。私は自分のエンジン設計でこれを確認しました。
Chris D.


4
@ChrisWわかりましたが、これらの値は長方形、つまり「単一」の値を表しているのではないですか?Vector3DやColorと同様に、これらは内部にもいくつかの値がありますが、単一の値を表していると思いますか?
Marson Mao

回答:


604

OPが参照するソースにはある程度の信頼性がありますが、Microsoftについてはどうですか-構造体の使用に関するスタンスは何ですか?私はマイクロソフトに追加の知識を求めました、そしてここに私が見つけたものがあります:

タイプのインスタンスが小さく、一般的に存続期間が短いか、一般的に他のオブジェクトに埋め込まれている場合は、クラスの代わりに構造を定義することを検討してください。

タイプに次のすべての特性がない限り、構造を定義しないでください。

  1. プリミティブ型(整数、ダブルなど)と同様に、論理的に単一の値を表します。
  2. インスタンスサイズは16バイト未満です。
  3. それは不変です。
  4. 頻繁にボックス化する必要はありません。

マイクロソフトは常にこれらのルールに違反しています

とにかく、#2と#3です。私たちの愛する辞書には、2つの内部構造があります。

[StructLayout(LayoutKind.Sequential)]  // default for structs
private struct Entry  //<Tkey, TValue>
{
    //  View code at *Reference Source
}

[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator : 
    IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, 
    IDictionaryEnumerator, IEnumerator
{
    //  View code at *Reference Source
}

* 参照ソース

'JonnyCantCode.com'のソースは4点中3点です。#4はおそらく問題ではないので、かなり許されます。構造体をボクシングしている場合は、アーキテクチャを考え直してください。

マイクロソフトがこれらの構造体を使用する理由を見てみましょう。

  1. 各構造体、EntryおよびEnumerator、単一の値を表します。
  2. 速度
  3. Entry辞書クラスの外でパラメーターとして渡されることはありません。さらなる調査により、IEnumerableの実装を満たすために、Dictionaryは、Enumerator列挙子が要求されるたびにコピーする構造体を使用することがわかります。
  4. 辞書クラスの内部。Enumeratorディクショナリは列挙可能であり、IEnumeratorインターフェイス実装(IEnumeratorゲッターなど)への同等のアクセシビリティを持つ必要があるため、パブリックです。

更新 -さらに、構造体がEnumeratorと同様にインターフェイスを実装し、その実装された型にキャストされると、構造体は参照型になり、ヒープに移動されることを理解してください。Enumerator 、Dictionaryクラスの内部も値型です。ただし、メソッドがを呼び出すとすぐGetEnumerator()に、参照タイプIEnumeratorが返されます。

ここに表示されていないのは、構造体を不変に保つ、またはインスタンスサイズを16バイト以下に維持するための試みまたは要件の証明です。

  1. 上記の構造体では何も宣言されていませんreadonly - 不変ではありません
  2. これらの構造体のサイズは16バイトをはるかに超える可能性があります
  3. Entry(から未定寿命を持っているAdd()と、Remove()Clear()、またはガベージコレクション)。

そして... 4.どちらの構造体もTKeyとTValueを格納します。これらはすべて参照型であることがかなりわかっています(ボーナス情報を追加)。

ハッシュ化されたキーにもかかわらず、構造体のインスタンス化は参照型よりも速いため、辞書は高速です。ここでは、Dictionary<int, int>300,000個のランダムな整数を格納し、順次インクリメントするキーを持っています。

容量:312874
Mem
サイズ:2660827バイト完了リサイズ:5ms
合計充填時間:889ms

容量:内部配列のサイズを変更する前に使用可能な要素の数。

MemSize:辞書をMemoryStreamにシリアル化し、バイト長を取得することで決定されます(この目的には十分正確です)。

完了したサイズ変更:内部配列のサイズを150862要素から312874要素に変更するのにかかる時間。各要素がを介して順次コピーされることを理解するとArray.CopyTo()、それはあまりにも粗末ではありません。

充填するまでの合計時間:ロギングとOnResizeソースに追加したイベントのために確かに歪んでいます。ただし、操作中に15倍のサイズ変更中に300kの整数を埋めることは依然として印象的です。好奇心から、容量をすでに知っている場合、合計でどのくらいの時間がかかりますか?13ms

では、Entryクラスだとしたら?これらの時間または測定基準は本当にそれほど異なりますか?

容量:312874
Mem
サイズ:2660827バイト完了リサイズ:26ms
合計充填時間:964ms

明らかに、大きな違いはサイズ変更です。辞書が容量で初期化されている場合、何か違いはありますか?気にするのに十分ではありません... 12ms

何が起こるかというEntryと、構造体であるため、参照型のような初期化は必要ありません。これは、値型の美しさと悩みの両方です。Entry参照型として使用するには、次のコードを挿入する必要がありました。

/*
 *  Added to satisfy initialization of entry elements --
 *  this is where the extra time is spent resizing the Entry array
 * **/
for (int i = 0 ; i < prime ; i++)
{
    destinationArray[i] = new Entry( );
}
/*  *********************************************** */  

の各配列要素をEntry参照型として初期化する必要があった理由は、MSDN:Structure Designにあります。要するに:

構造体にはデフォルトのコンストラクタを提供しないでください。

構造体がデフォルトのコンストラクターを定義している場合、構造体の配列が作成されると、共通言語ランタイムは各配列要素でデフォルトのコンストラクターを自動的に実行します。

C#コンパイラなどの一部のコンパイラでは、構造体にデフォルトのコンストラクタを設定できません。

それは実際には非常に単純であり、我々はアシモフのロボット工学3つの法則から借ります:

  1. 構造体は安全に使用できる必要があります
  2. ルール#1に違反しない限り、構造体はその機能を効率的に実行する必要があります。
  3. ルール#1を満たすために破壊が必要でない限り、構造体は使用中もそのままでなければなりません。

... これから何を取り除きますか?つまり、値型の使用に責任があります。これらは迅速かつ効率的ですが、適切にメンテナンスされていないと、予期しない動作(つまり、意図しないコピー)を引き起こす可能性があります。


8
Microsoftのルールについては、不変性についてのルールは、彼らの行動があるという事実にもかかわらず、参照型のものとは異なるであろうと、このようなAの方法で、値型の使用を阻止するために設計されているように見える区分的可変値のセマンティクスが有用であることができます。タイプを区分的に変更可能にすると、作業が容易になり、タイプの格納場所を互いに論理的に分離する必要がある場合、タイプは「変更可能」構造体にする必要があります。
スーパーキャット2012


2
Microsoftの型の多くがこれらの規則に違反しているという事実は、それらの型の問題を表すものではなく、規則がすべての構造型に適用されるわけではないことを示しています。構造体が[ DecimalまたはDateTime]のように単一のエンティティを表す場合、他の3つのルールを遵守しない場合は、クラスで置き換える必要があります。構造が変数の固定コレクションを保持し、それぞれがそのタイプに有効な任意の値を保持する可能性がある場合(例Rectangle:)、異なる規則に従う必要があります。その一部は「単一値」構造の規則に反しています。 。
スーパーキャット2013年

4
@IAbstract:一部の人々は、Dictionaryエントリタイプが内部タイプのみであること、パフォーマンスがセマンティクスより重要であると考えられていること、またはその他の言い訳に基づいてエントリタイプを正当化します。私のポイントは以下のようなタイプがあることでRectangle、パフォーマンス上の利点は、結果の意味的な欠陥を上回る「ので」、その内容を個別に編集可能なフィールドではないとして公開されている必要がありますが、ため、タイプは意味的に独立した値の固定セットを表し、そして可変構造体の両方があるので、よりパフォーマンスが高く、意味的に優れています。
スーパーキャット2013年

2
@supercat:私は同意します...そして私の答えの要点は、「ガイドライン」はかなり弱く、構造は動作の完全な知識と理解とともに使用されるべきであるということでした。ここで変更可能な構造体に私の答えを参照してください。stackoverflow.com/questions/8108920/...
IAbstract

155

いつでも:

  1. 多態性は必要ありません、
  2. 値の意味論が欲しい
  3. ヒープ割り当てと関連するガベージコレクションのオーバーヘッドを回避したい。

ただし、注意点は、構造体(任意に大きい)はクラス参照(通常は1つのマシンワード)よりも渡すのにコストがかかるため、実際にはクラスが高速になる可能性があるということです。


1
これは「警告」の1つにすぎません。(Guid)nullとりわけ、値タイプの「リフティング」や(nullを参照タイプにキャストしても問題ありません)などのケースも考慮する必要があります。

1
C / C ++よりも高価ですか?C ++では、オブジェクトを値で渡すことをお勧めします
Ion Todirel 2013

@IonTodirelパフォーマンスではなく、メモリの安全性の理由ではありませんでしたか?これは常にトレードオフですが、スタックで32 Bを渡すと、常に4B参照をレジスタで渡すよりも常に(TM)遅くなります。ただし、「値/参照」の使用はC#とC ++では少し異なることに注意してください。オブジェクトへの参照を渡す場合、参照を渡しても(値を渡します)、参照への参照ではなく、参照の値を再度渡します)。これは値のセマンティクスではありませんが、技術的には「値渡し」です。
Luaan、2015年

@Luaanコピーはコストの1つの側面にすぎません。ポインター/参照による追加の間接参照も、アクセスごとのコストです。場合によっては、構造体を移動することもできるため、コピーする必要もありません。
Onur

@Onur面白いですね。コピーせずに「移動」するにはどうすればよいですか?asmの「mov」命令は実際には「移動」しないと思いました。コピーします。
Winger Sendon 2017

148

元の投稿に記載されているルールに同意しません。ここに私のルールがあります:

1)配列に格納する場合、パフォーマンスのために構造体を使用します。(構造体がいつ解答するのか参照してください

2)C / C ++との間で構造化データを渡すコードでそれらが必要です。

3)必要な場合を除き、構造体は使用しないでください。

  • 「通常のオブジェクト」(参照タイプ)とは異なる動作をします、割り当て時および引数として渡す場合の)をするため、予期しない動作が発生する可能性があります。コードを見ている人が構造体を扱っていることを知らない場合、これは特に危険です。
  • 継承することはできません。
  • 構造体を引数として渡すことは、クラスよりもコストがかかります。

4
+1はい、私は#1に完全に同意します(これは画像などを処理するときに大きな利点です)、それら「通常のオブジェクト」とは異なり、既存の知識を除いてこれ知る方法わかっていることを指摘しますまたはタイプ自体を調べます。また、null値を構造体型にキャストすることはできません:-)これは実際には、非コア値型の「ハンガリー語」または変数宣言サイトに必須の「構造体」キーワードが存在することを望むほとんどの場合です。

@pst:何かがstructそれがどのように動作するかを知るためにあることを知っている必要があるのは本当ですが、何かがstruct公開されたフィールドを持つものである場合、それはすべて知っておく必要があります。オブジェクトがexposed-field-struct型のプロパティを公開し、コードがその構造体を変数に読み取って変更した場合、そのようなアクションは、構造体が書き込まれるまで、または書き込まれるまで、プロパティが読み取られたオブジェクトに影響を与えないと予測できます。バック。対照的に、プロパティが変更可能なクラスタイプである場合、それを読み取って変更すると、基になるオブジェクトが期待どおりに更新される可能性がありますが...
supercat

...最終的に何も変更されない場合もあれば、変更するつもりのなかったオブジェクトが変更または破損する場合もあります。セマンティクスが「この変数を好きなように変更する;変更はどこかに明示的に保存するまで何もしない」というコードを持つことは、「任意の数と共有する可能性があるオブジェクトへの参照を取得している」というコードを持つことよりも明確に見える他の参照の場合、またはまったく共有されない場合があります。変更した場合にどうなるかを知るには、このオブジェクトへの参照を他に持っている可能性がある人を把握する必要があります。」
スーパーキャット2012

#1を見つけてください。構造体の完全なリストは、オブジェクト参照の完全なリストよりもはるかに関連性の高いデータをL1 / L2キャッシュに圧縮できます(適切なサイズの構造体の場合)。
Matt Stephenson、

2
継承がジョブに適したツールであることはめったになく、プロファイリングなしでパフォーマンスについて過度に推論することは悪い考えです。まず、構造体は参照で渡すことができます。第2に、参照渡しまたは値渡しがパフォーマンス上の大きな問題になることはほとんどありません。最後に、クラスで実行する必要がある追加のヒープ割り当てとガベージコレクションを考慮していません。個人的には、構造体をプレーン・オールド・データと見なし、クラスを何かを行うもの(オブジェクト)と見なすこともできますが、構造体にメソッドを定義することもできます。
weberc2 2015年

88

参照セマンティクスではなく値セマンティクスが必要な場合は、構造体を使用します。

編集する

なぜ人々がこれに反対票を投じているのかはわかりませんが、これは妥当な点であり、オペレーションが彼の質問を明確にする前に行われました、そしてそれは構造の最も基本的な基本的な理由です。

参照セマンティクスが必要な場合は、構造体ではなくクラスが必要です。


13
誰もが知っています。彼は「構造体は値の型です」という答え以上のものを探しているようです。
TheSmurf 2009

21
これは最も基本的なケースであり、この投稿を読んでそれを知らない人のために述べるべきです。
JoshBerke、2009

3
この答えは真実ではありません。明らかにそうです。それは本当の意味ではありません。
TheSmurf 2009

55
@ジョシュ:まだそれを知らない人にとっては、それが何を意味するのかわからない可能性が高いので、単にそれは不十分な答えであると言うだけです。
TheSmurf 2009

1
他の回答の1つが一番上にあると思うので、これに反対票を投じました。「アンマネージコードとの相互運用の場合は、それ以外の場合は避けてください」という回答。
ダニエルエリカー

59

「それは価値がある」という回答に加えて、構造体を使用する特定のシナリオの1つは、ガベージコレクションの問題を引き起こしているデータのセットがあり、オブジェクトがたくさんあることがわかっている場合です。たとえば、Personインスタンスの大きなリスト/配列。ここでの自然なメタファーはクラスですが、長期間有効なPersonインスタンスが多数ある場合、GEN-2が詰まり、GCストールが発生する可能性があります。シナリオワラントそれならば、ここで1つの潜在的なアプローチは、人物の配列(リストではなく)を使用することで、構造体、すなわちPerson[]。これで、GEN-2に数百万のオブジェクトが存在する代わりに、LOHに単一のチャンクが存在します(ここでは文字列などがないと想定しています。つまり、参照のない純粋な値です)。これによるGCへの影響はほとんどありません。

データはおそらく構造体に対して大きすぎるため、このデータの操作は厄介であり、脂肪の値を常にコピーする必要はありません。ただし、配列で直接アクセスしても、構造体はコピーされません-インプレースです(コピーを行うリストインデクサーとは対照的です)。これは、インデックスに関する多くの作業を意味します。

int index = ...
int id = peopleArray[index].Id;

値自体を不変に保つことは、ここで役立つことに注意してください。より複雑なロジックの場合は、参照パラメータを持つメソッドを使用します。

void Foo(ref Person person) {...}
...
Foo(ref peopleArray[index]);

繰り返しますが、これはインプレースです。値をコピーしていません。

非常に具体的なシナリオでは、この戦術は非常に成功する可能性があります。ただし、これはかなり高度なシナリオであり、自分が何をしているか、そしてその理由を知っている場合にのみ試行する必要があります。ここでのデフォルトはクラスです。


+1興味深い答え。そのようなアプローチが使用されていることについて、現実世界の逸話を共有してもかまいませんか?
ジョルダン

@Jordaoをモバイルで使用するが、googleで検索:+ gravell + "assault by GC"
Marc Gravell

1
どうもありがとう。ここで見つけ
2012年

2
@MarcGravellなぜあなたは言及しましたか:(リストではなく)配列を使用しますか?List私は、Array背後のシーンを使用すると思います。番号 ?
Royi Namir 14

4
@RoyiNamir私もこれに興味がありましたが、答えはマークの回答の2番目の段落にあると思います。「ただし、配列内で直接アクセスしても構造体はコピーされません-インプレースです(コピーを行うリストインデクサーとは対照的です。)」
user1323245 2014

40

C#言語仕様

1.7構造

クラスと同様に、構造体はデータメンバーと関数メンバーを含むことができるデータ構造ですが、クラスとは異なり、構造体は値型であり、ヒープの割り当てを必要としません。構造体型の変数は構造体のデータを直接格納しますが、クラス型の変数は動的に割り当てられたオブジェクトへの参照を格納します。構造体型はユーザー指定の継承をサポートしておらず、すべての構造体型は暗黙的に型オブジェクトから継承します。

構造体は、値のセマンティクスを持つ小さなデータ構造に特に役立ちます。複素数、座標系のポイント、またはディクショナリのキーと値のペアはすべて、構造体の良い例です。小さなデータ構造に対してクラスではなく構造体を使用すると、アプリケーションが実行するメモリ割り当ての数に大きな違いが生じる可能性があります。たとえば、次のプログラムは100ポイントの配列を作成して初期化します。Pointがクラスとして実装されると、101個の個別のオブジェクトがインスタンス化されます。1つは配列用、もう1つは100個の要素用です。

class Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

class Test
{
   static void Main() {
      Point[] points = new Point[100];
      for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
   }
}

別の方法は、ポイントを構造体にすることです。

struct Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

これで、インスタンス化されるオブジェクトは1つ(配列のオブジェクト)だけになり、Pointインスタンスは配列にインラインで格納されます。

構造体コンストラクタはnew演算子で呼び出されますが、メモリが割り当てられていることを意味するものではありません。オブジェクトを動的に割り当ててオブジェクトへの参照を返す代わりに、構造体コンストラクターは単に構造体の値自体(通常はスタック上の一時的な場所)を返し、この値は必要に応じてコピーされます。

クラスを使用すると、2つの変数が同じオブジェクトを参照することが可能になり、1つの変数に対する操作が、他の変数によって参照されるオブジェクトに影響を与える可能性があります。構造体を使用すると、変数にはそれぞれデータの独自のコピーがあり、一方の操作が他方に影響を与えることはできません。たとえば、次のコードフラグメントによって生成される出力は、Pointがクラスか構造体かによって異なります。

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

Pointがクラスの場合、aとbが同じオブジェクトを参照するため、出力は20です。Pointが構造体の場合、aをbに割り当てると値のコピーが作成されるため、出力は10です。このコピーは、その後のaxへの割り当ての影響を受けません。

前の例では、構造体の2つの制限を強調しています。第1に、構造体全体をコピーすることは、通常、オブジェクト参照をコピーするよりも効率が悪いため、代入と値パラメーターの受け渡しは、参照型を使用するよりも構造体を使用する方がコストが高くなる可能性があります。第2に、refおよびoutパラメータを除いて、構造体への参照を作成することはできません。これにより、多くの状況での使用が禁止されます。


4
構造体への参照を永続化できないという事実は時々制限ですが、それは非常に有用な特性でもあります。.netの主な弱点の1つは、可変オブジェクトへの参照を外部コードに渡して、そのオブジェクトの制御を永久に失うことなく適切な方法がないことです。対照的に、外部メソッドa refを変更可能な構造体に安全に与えて、外部メソッドがその構造体に対して実行するすべての変更は、それが戻る前に行われることを知っています。
残念

4
...これにより、渡されrefた構造体の有利なセマンティクスをクラスオブジェクトで実現できます。基本的に、ローカル変数、パラメーター、および関数の戻り値は、永続可能(デフォルト)、戻り可能、または一時的である可能性があります。コードは、一時的なものを現在の範囲を超えるものにコピーすることを禁止されます。返却可能なものは、関数から返される可能性があることを除いて、一時的なものと同じです。関数の戻り値は、その「戻り可能」パラメーターのいずれにも適用できる最も厳しい制限に拘束されます。
スーパーキャット2012

34

構造体は、データをアトミックに表現するのに適しています。このデータは、コードによって複数回コピーできます。オブジェクトのクローン作成は、メモリの割り当て、コンストラクターの実行、およびそれを使用した場合の割り当て解除/ガベージコレクションを伴うため、構造体をコピーするよりも一般にコストが高くなります。


4
はい。ただし、大きな構造体はクラス参照よりも高価になる可能性があります(メソッドに渡す場合)。
Alex

27

ここに基本的なルールがあります。

  • すべてのメンバーフィールドが値型の場合は、構造体を作成します。

  • いずれかのメンバーフィールドが参照型の場合は、クラスを作成します。これは、参照タイプのフィールドにはとにかくヒープの割り当てが必要になるためです。

public struct MyPoint 
{
    public int X; // Value Type
    public int Y; // Value Type
}

public class MyPointWithName 
{
    public int X; // Value Type
    public int Y; // Value Type
    public string Name; // Reference Type
}

3
のような不変参照型stringは、意味的に値と同等であり、不変オブジェクトへの参照をフィールドに格納しても、ヒープ割り当ては必要ありません。露出されたパブリックフィールドを持つ構造体と露出パブリックフィールドを持つクラスのオブジェクトとの間の差は、コード配列与えつまりvar q=p; p.X=4; q.X=5;p.X場合に値4を持つことになりa、それがクラス型かどう構造型であり、5。1つの願いが便利なタイプのメンバーを変更することができるようにする場合は、1は1つがに変更望んでいるかどうかに基づいて「クラス」や「構造体」を選択する必要がありますq影響を与えるがp
スーパーキャット2014年

はい、参照変数はスタック上にありますが、参照するオブジェクトはヒープ上に存在することに同意します。構造体とクラスは別の変数に割り当てられたときに異なる動作をしますが、それは強力な決定要因だとは思いません。
ウスマンザファール2014年

可変構造体と可変クラスの動作は完全に異なります。一方が正しい場合、もう一方はおそらく間違っているでしょう。構造体とクラスのどちらを使用するかを決定する際に、動作が決定的な要素にならないかどうかはわかりません。
スーパーキャット2014年

多くの場合、クラスまたは構造体を作成するときに、それがどのように使用されるかわからないため、これは強力な決定要因ではないと述べました。だから、デザインの観点から物事がより意味のある方法に集中します。とにかく、構造体に参照変数が含まれている.NETライブラリの1か所で見たことがない。
Usman Zafar 2014年

1
構造型は、常にクラス型であるをArraySegment<T>カプセル化しT[]ます。構造体型KeyValuePair<TKey,TValue>は、一般的なパラメーターとしてクラス型と共に使用されることがよくあります。
スーパーキャット2014年

19

最初:相互運用シナリオ、またはメモリレイアウトを指定する必要がある場合

2番目:いずれにしても、データが参照ポインターとほぼ同じサイズの場合。


17

StructLayoutAttribute(通常はPInvokeの場合)を使用してメモリレイアウトを明示的に指定する場合は、「構造体」を使用する必要があります。

編集:コメントは、クラスまたは構造体をStructLayoutAttributeで使用できることを指摘しています。これは確かに当てはまります。実際には、通常、構造体を使用します-構造体はスタックとヒープで割り当てられます。これは、アンマネージメソッドの呼び出しに引数を渡すだけの場合に意味があります。


5
StructLayoutAttributeは構造体またはクラスに適用できるため、構造体を使用する理由にはなりません。
スティーブンマーティン

アンマネージメソッドの呼び出しに引数を渡すだけの場合、なぜ意味があるのでしょうか。
David Klempfner 2017年

16

私は、あらゆる種類のバイナリ通信形式をパックまたはアンパックするために構造体を使用しています。これには、ディスクの読み取りまたは書き込み、DirectX頂点リスト、ネットワークプロトコル、または暗号化/圧縮されたデータの処理が含まれます。

あなたが挙げた3つのガイドラインは、この文脈では私にとって役に立たなかった。400バイトのものを特定の順序で書き出す必要がある場合は、400バイトの構造体を定義し、それに関係のないすべての値を入れて、その時点で最も理にかなった方法で設定します。(さて、400バイトはかなり奇妙です-しかし、私が生活のためにExcelファイルを書き込んでいたとき、私はすべて約40バイトまでの構造体を扱っていました、それはそれがBIFFレコードのいくつかがどれほど大きいかであるからです。)


しかし、そのために参照型を簡単に使用できませんか?
David Klempfner 2017年

15

PInvokeの目的でランタイムやその他のさまざまなものによって直接使用されるvaluetypeを除いて、valuetypeは2つのシナリオでのみ使用する必要があります。

  1. コピーのセマンティクスが必要な場合。
  2. 自動初期化が必要な場合、通常はこれらのタイプの配列。

#2があると思われる一部の.NETコレクションクラスで構造体の有病率の理由の...
IAbstract

クラス型の格納場所を作成するときに最初に行うことは、その型の新しいインスタンスを作成し、その場所への参照を格納し、他の場所に参照をコピーしたり、上書きしたりしないことです。次に、構造体クラスは同じように動作します。構造体は、すべてのフィールドを1つのインスタンスから別のインスタンスにコピーする便利な標準的な方法を備えており、クラスへの参照を決して複製しない場合に、通常はより優れたパフォーマンスを提供します(thisメソッドの呼び出しに使用される一時パラメーターを除く)。クラスを使用すると、参照を複製できます。
スーパーキャット2013

13

.NETはvalue typesおよびをサポートしますreference types(Javaでは、参照型のみを定義できます)。のインスタンスはreference typesマネージヒープに割り当てられ、インスタンスへの未解決の参照がない場合はガベージコレクションされます。value types一方、のインスタンスはに割り当てられるstackため、割り当てられたメモリはスコープが終了するとすぐに再利用されます。そしてもちろん、value types値とreference types参照によって渡されます。System.Stringを除くすべてのC#プリミティブデータ型は値型です。

クラスで構造体を使用する場合、

C#では、structsあるvalue types、クラスがありますreference typesenumキーワードとキーワードを使用して、C#で値タイプを作成できますstruct。aのvalue type代わりにを使用するreference typeと、マネージヒープ上のオブジェクトが少なくなり、ガベージコレクター(GC)への負荷が減り、GCサイクルの頻度が減り、結果的にパフォーマンスが向上します。ただし、value types欠点もあります。大きなstructものを渡すことは、リファレンスを渡すよりも明らかにコストがかかります。これは明らかな問題の1つです。もう1つの問題は、に関連するオーバーヘッドboxing/unboxingです。どういうboxing/unboxing意味かわからない場合は、これらのリンクをたどってboxingunboxing。パフォーマンスとは別に、型が値のセマンティクスを持つ必要があるだけの場合reference typesあなたが持っているすべてです。value typesコピーのセマンティクスが必要な場合、または通常arraysこれらのタイプの自動初期化が必要な場合にのみ使用してください。


小さな構造のコピーまたは値による受け渡しは、クラス参照のコピーまたは受け渡し、あるいはによる構造の受け渡しと同じくらい簡単refです。任意のサイズの構造体refを渡すことは、値によってクラス参照を渡すことと同じコストです。サイズ構造をコピーしたり、値で渡したりする方が、クラスオブジェクトの防御コピーを実行してそれへの参照を保存または渡すよりも安価です。大きな時間のクラスがある(1)クラスは(守備のコピーを避けるためになるように)不変であり、作成される各インスタンスはたくさんの周りに渡される、または...の値を格納するための構造体よりも優れている
supercat

...(2)さまざまな理由で構造体が単に使用できない場合[たとえば、ツリーのようなものにネストされた参照を使用する必要があるため、またはポリモーフィズムが必要なため]。値型を使用する場合、通常、特定の理由がない場合にフィールドを直接公開する必要があることに注意してください(ほとんどのクラス型の場合、フィールドはプロパティ内にラップする必要があります)。可変値型のいわゆる「悪魔」の多くは、プロパティ内のフィールドの不必要なラッピングに由来します(たとえば、一部のコンパイラーは、読み取り専用の構造体でプロパティセッターを呼び出すことを許可する場合があります...
supercat

...正しいことを行うと、すべてのコンパイラはそのような構造体にフィールドを直接設定する試みを適切に拒否します。コンパイラが確実に拒否する最善の方法readOnlyStruct.someMember = 5;someMember、読み取り専用プロパティを作成することではなく、フィールドにすることです。
スーパーキャット2012

12

構造体は値型です。構造体を新しい変数に割り当てると、新しい変数には元の変数のコピーが含まれます。

public struct IntStruct {
    public int Value {get; set;}
}

以下を実行すると、メモリに格納された構造体の5つのインスタンスが生成されます。

var struct1 = new IntStruct() { Value = 0 }; // original
var struct2 = struct1;  // A copy is made
var struct3 = struct2;  // A copy is made
var struct4 = struct3;  // A copy is made
var struct5 = struct4;  // A copy is made

// NOTE: A "copy" will occur when you pass a struct into a method parameter.
// To avoid the "copy", use the ref keyword.

// Although structs are designed to use less system resources
// than classes.  If used incorrectly, they could use significantly more.

クラスは参照型です。クラスを新しい変数に割り当てると、変数には元のクラスオブジェクトへの参照が含まれます。

public class IntClass {
    public int Value {get; set;}
}

次のコードを実行すると、メモリ内にクラスオブジェクトのインスタンス1つだけ生成されます。

var class1 = new IntClass() { Value = 0 };
var class2 = class1;  // A reference is made to class1
var class3 = class2;  // A reference is made to class1
var class4 = class3;  // A reference is made to class1
var class5 = class4;  // A reference is made to class1  

Structは、コードミスの可能性を高めます。値オブジェクトが変更可能な参照オブジェクトのように扱われる場合、行われた変更が予期せず失われると、開発者は驚くかもしれません。

var struct1 = new IntStruct() { Value = 0 };
var struct2 = struct1;
struct2.Value = 1;
// At this point, a developer may be surprised when 
// struct1.Value is 0 and not 1

12

BenchmarkDotNetで小さなベンチマークを作成しました「構造体」のメリットを数値でよりよく理解しました。構造体(またはクラス)の配列(またはリスト)をループしてテストしています。これらの配列またはリストを作成することは、ベンチマークの範囲外です。「クラス」の方が重いため、より多くのメモリを使用し、GCを伴うことは明らかです。

したがって、結論は次のとおりです。LINQと隠し構造体のボックス化/ボックス化解除に注意してください。

PSコールスタックを介してstruct / classを渡すことに関する別のベンチマークがありますhttps://stackoverflow.com/a/47864451/506147

BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233542 Hz, Resolution=309.2584 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Core   : .NET Core 4.6.25211.01, 64bit RyuJIT


          Method |  Job | Runtime |      Mean |     Error |    StdDev |       Min |       Max |    Median | Rank |  Gen 0 | Allocated |
---------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|----------:|-----:|-------:|----------:|
   TestListClass |  Clr |     Clr |  5.599 us | 0.0408 us | 0.0382 us |  5.561 us |  5.689 us |  5.583 us |    3 |      - |       0 B |
  TestArrayClass |  Clr |     Clr |  2.024 us | 0.0102 us | 0.0096 us |  2.011 us |  2.043 us |  2.022 us |    2 |      - |       0 B |
  TestListStruct |  Clr |     Clr |  8.427 us | 0.1983 us | 0.2204 us |  8.101 us |  9.007 us |  8.374 us |    5 |      - |       0 B |
 TestArrayStruct |  Clr |     Clr |  1.539 us | 0.0295 us | 0.0276 us |  1.502 us |  1.577 us |  1.537 us |    1 |      - |       0 B |
   TestLinqClass |  Clr |     Clr | 13.117 us | 0.1007 us | 0.0892 us | 13.007 us | 13.301 us | 13.089 us |    7 | 0.0153 |      80 B |
  TestLinqStruct |  Clr |     Clr | 28.676 us | 0.1837 us | 0.1534 us | 28.441 us | 28.957 us | 28.660 us |    9 |      - |      96 B |
   TestListClass | Core |    Core |  5.747 us | 0.1147 us | 0.1275 us |  5.567 us |  5.945 us |  5.756 us |    4 |      - |       0 B |
  TestArrayClass | Core |    Core |  2.023 us | 0.0299 us | 0.0279 us |  1.990 us |  2.069 us |  2.013 us |    2 |      - |       0 B |
  TestListStruct | Core |    Core |  8.753 us | 0.1659 us | 0.1910 us |  8.498 us |  9.110 us |  8.670 us |    6 |      - |       0 B |
 TestArrayStruct | Core |    Core |  1.552 us | 0.0307 us | 0.0377 us |  1.496 us |  1.618 us |  1.552 us |    1 |      - |       0 B |
   TestLinqClass | Core |    Core | 14.286 us | 0.2430 us | 0.2273 us | 13.956 us | 14.678 us | 14.313 us |    8 | 0.0153 |      72 B |
  TestLinqStruct | Core |    Core | 30.121 us | 0.5941 us | 0.5835 us | 28.928 us | 30.909 us | 30.153 us |   10 |      - |      88 B |

コード:

[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkRef
    {
        public class C1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        public struct S1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        List<C1> testListClass = new List<C1>();
        List<S1> testListStruct = new List<S1>();
        C1[] testArrayClass;
        S1[] testArrayStruct;
        public BenchmarkRef()
        {
            for(int i=0;i<1000;i++)
            {
                testListClass.Add(new C1  { Text1= i.ToString(), Text2=null, Text3= i.ToString() });
                testListStruct.Add(new S1 { Text1 = i.ToString(), Text2 = null, Text3 = i.ToString() });
            }
            testArrayClass = testListClass.ToArray();
            testArrayStruct = testListStruct.ToArray();
        }

        [Benchmark]
        public int TestListClass()
        {
            var x = 0;
            foreach(var i in testListClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayClass()
        {
            var x = 0;
            foreach (var i in testArrayClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestListStruct()
        {
            var x = 0;
            foreach (var i in testListStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayStruct()
        {
            var x = 0;
            foreach (var i in testArrayStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestLinqClass()
        {
            var x = testListClass.Select(i=> i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }

        [Benchmark]
        public int TestLinqStruct()
        {
            var x = testListStruct.Select(i => i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }
    }

リストなどで使用すると、構造体の速度が非常に遅くなる理由を理解しましたか?それはあなたが言った隠されたボクシングとアンボクシングのせいですか?もしそうなら、なぜそれが起こるのですか?
Marko Grdinic 2017

配列内の構造体へのアクセスは、追加の参照が必要ないという理由だけで速くなるはずです。ボクシング/アンボクシングはlinqの場合です。
Roman Pokrovskij 2017

10

C#または他の.net言語の構造体型は、通常、固定サイズの値のグループのように動作する必要があるものを保持するために使用されます。構造タイプの有用な側面は、構造タイプインスタンスのフィールドは、それが保持されているストレージの場所を変更することによってのみ変更できることです。フィールドを変更する唯一の方法はまったく新しいインスタンスを作成し、構造体の割り当てを使用して新しいインスタンスの値でフィールドを上書きすることでターゲットのすべてのフィールドを変更するように構造をコーディングすることは可能ですが、構造体がフィールドにデフォルト値以外のインスタンスを作成する手段を提供しない限り、構造体自体が変更可能な場所に格納されている場合、そのすべてのフィールドは変更可能になります。

構造体にプライベートクラスタイプフィールドが含まれ、その独自のメンバーをラップされたクラスオブジェクトのメンバーにリダイレクトする場合、構造体タイプを本質的にクラスタイプのように動作するように設計できることに注意してください。たとえば、a PersonCollectionはプロパティSortedByNameとを提供しSortedById、どちらもPersonCollection(コンストラクタで設定された)への「不変」参照を保持GetEnumeratorてを呼び出すことで実装しcreator.GetNameSortedEnumeratorまたはのかをcreator.GetIdSortedEnumerator。そのような構造体はPersonCollection、それらのGetEnumeratorメソッドがの異なるメソッドにバインドされることを除いて、への参照とほとんど同じように動作しPersonCollectionます。配列の一部をラップする構造を持つこともできます(たとえばArrayRange<T>T[]呼び出されArrたintを保持する構造を定義することもできます)Offset、およびLength、インデックス付きプロパティを使用して、idx0からの範囲のインデックスに対してLength-1アクセスしますArr[idx+Offset])。残念ながら、fooがそのような構造の読み取り専用インスタンスである場合、現在のコンパイラバージョンでは、foo[3]+=4;そのような操作がのフィールドに書き込もうとするかどうかを判断する方法がないため、このような操作は許可されませんfoo

可変サイズのコレクション(構造体がコピーされるたびにコピーされるように見える)を保持する値型のように動作するように構造を設計することもできますが、その動作を行う唯一の方法は、構造体は参照を保持し、それを変更する可能性のあるものに公開されます。たとえば、プライベート配列を保持する配列のような構造体があり、そのインデックス付きの「put」メソッドは、1つの変更された要素を除いて、内容が元の配列と同じである新しい配列を作成します。残念ながら、そのような構造体を効率的に実行させることは多少難しい場合があります。構造体のセマンティクスが便利な場合があります(たとえば、配列のようなコレクションをルーチンに渡すことができ、呼び出し元と呼び出し先の両方が外部コードがコレクションを変更しないことを知っている場合)


10

いや-私は完全にルールに同意しません。これらは、パフォーマンスと標準化について検討するのに適したガイドラインですが、可能性を考慮したものではありません。

応答でわかるように、それらを使用する多くの創造的な方法があります。したがって、これらのガイドラインは、パフォーマンスと効率のために常にそうである必要があります。

この場合、クラスを使用して実世界のオブジェクトをより大きな形式で表し、構造体を使用してより正確な用途を持つ小さなオブジェクトを表します。あなたがそれを言った方法、「よりまとまりのある全体」。キーワードはまとまりがあります。クラスはよりオブジェクト指向の要素になりますが、構造体はこれらの特性の一部を持つことができますが、規模は小さくなります。IMO。

一般的な静的属性にすばやくアクセスできるTreeviewおよびListviewタグでこれらを頻繁に使用します。私はいつもこの情報を別の方法で取得するのに苦労してきました。たとえば、私のデータベースアプリケーションでは、テーブル、SP、関数、またはその他のオブジェクトがあるツリービューを使用しています。構造体を作成してデータを入力し、タグに挿入して引き出し、選択範囲のデータを取得します。私はこれをクラスではしません!

私はそれらを小さくして、単一インスタンスの状況で使用し、変更されないようにしています。メモリ、割り当て、パフォーマンスに注意することは賢明です。そしてテストはとても必要です。


構造体は、軽量の不変オブジェクトを表すために使用したり、関連するが独立した変数の固定セット(たとえば、点の座標)を表すために使用したりすることができます。そのページのアドバイスは、前者の目的を果たすように設計された構造体には適していますが、後者の目的を果たすように設計された構造体には間違っています。私の現在の考えでは、プライベートフィールドを持つ構造体は通常、指定された説明を満たす必要がありますが、多くの構造体はパブリックフィールドを介して状態全体を公開する必要があります。
スーパーキャット2013

「3Dポイント」タイプの仕様が、その全体の状態が読み取り可能なメンバーx、y、およびzを介して公開されることをdouble示し、これらの座標の値の任意の組み合わせでインスタンスを作成することが可能である場合、そのような仕様はそれを強制します。マルチスレッドの動作の一部の詳細を除いて、公開フィールド構造と意味的に同じように動作します(不変クラスの方が優れている場合もあれば、公開フィールド構造の方が優れている場合もあります。いわゆる「不変」構造はすべてのケースで悪化する)。
スーパーキャット2013

8

私のルールは

1、常にクラスを使用します。

2、パフォーマンスの問題がある場合は、@ IAbstractが言及したルールに応じて、いくつかのクラスをstructに変更し、これらの変更によってパフォーマンスが向上するかどうかをテストします。


Microsoftが無視する実質的な使用例は、型の変数にFoo独立した値の固定されたコレクション(たとえば、点の座標)をカプセル化させたい場合であり、グループとして渡したい場合や独立して変更したい場合があります。両方の目的を単純な公開フィールド構造体(独立変数の固定されたコレクションであり、法案に完全に適合します)とほぼ同じように組み合わせるクラスを使用するためのパターンは見つかりませんでした。
スーパーキャット2013

1
@supercat:Microsoftのせいにすることは完全に公平ではないと思います。ここでの本当の問題は、オブジェクト指向言語としてのC#が、あまり動作せずにデータを公開するだけの単純なレコードタイプに焦点を合わせていないことです。C#は、C ++などのマルチパラダイム言語ではありません。そうは言って、純粋なOOPをプログラミングしている人はほとんどいないと思うので、おそらくC#は言語として理想的過ぎるでしょう。(私も最近public readonly、自分のタイプのフィールドを公開し始めました。なぜなら、読み取り専用プロパティを作成することは、実際には何の利益にもならないだけの作業です。)
stakx-2013年

1
@stakx:そのようなタイプに「集中」する必要はありません。彼らが何であるかについて彼らを認識することで十分でしょう。構造体に関するC#の最大の弱点は、他の多くの領域での最大の問題でもあります。言語は、特定の変換が適切であるか適切でないかを示すための不十分な機能を提供し、そのような機能の欠如が設計の不幸な決定を引き起こします。たとえば、「可変構造体は悪である」の99%は、コンパイラーがに変わったことMyListOfPoint[3].Offset(2,3);に由来しvar temp=MyListOfPoint[3]; temp.Offset(2,3);ます。これは、適用すると偽の変換になります...
supercat

... Offsetメソッドに。このような偽のコードを防ぐ適切な方法は、構造体を不必要に不変にすることではなくOffset、前述の変換を禁止する属性でタグ付けできるようなメソッドを許可することです。暗黙的な数値変換も、呼び出しが明らかな場合にのみ適用できるようにタグ付けできれば、はるかに優れていた可能性があります。foo(float,float)and foo(double,double)にオーバーロードが存在する場合、a floatとa を使用しようとするとdouble、暗黙の変換は適用されず、エラーになるはずです。
スーパーキャット2013

double値を直接割り当てるfloatか、またはfloat引数をとることはできるが、引数をとることができないメソッドに値を渡すとdouble、ほとんどの場合、プログラマの意図したとおりに動作します。対照的に、明示的な型キャストなしでfloat式をに割り当てることは、double多くの場合誤りです。暗黙的なdouble->float変換を許可するのは、問題が発生するのは、理想的ではないオーバーロードが選択されるときだけです。それを防ぐための正しい方法は、暗黙のdouble-> floatを禁止するべきではなく、変換を許可しない属性でオーバーロードにタグを付けることでした。
スーパーキャット2013

8

クラスは参照型です。クラスのオブジェクトが作成されると、オブジェクトが割り当てられている変数は、そのメモリへの参照のみを保持します。オブジェクト参照が新しい変数に割り当てられると、新しい変数は元のオブジェクトを参照します。1つの変数を介して行われた変更は、両方とも同じデータを参照するため、他の変数に反映されます。構造体は値型です。構造体が作成されると、構造体が割り当てられている変数は、構造体の実際のデータを保持します。構造体が新しい変数に割り当てられると、それがコピーされます。したがって、新しい変数と元の変数には、同じデータの2つの別々のコピーが含まれています。1つのコピーに加えられた変更は、他のコピーには影響しません。一般に、クラスは、より複雑な動作、またはクラスオブジェクトの作成後に変更することを目的としたデータをモデル化するために使用されます。

クラスと構造体(C#プログラミングガイド)


構造は、いくつかの関連しているが独立している変数をダクトテープ(たとえば、点の座標)と一緒に固定する必要がある場合にも非常に適しています。オブジェクトのように動作する構造を作成しようとする場合、MSDNガイドラインは妥当ですが、集計を設計する場合にはあまり適切ではありません。それらのいくつかは、後者の状況ではほぼ正確に間違っています。たとえば、型によってカプセル化された変数の独立度が高いほど、不変クラスではなく公開フィールド構造を使用する利点が大きくなります。
スーパーキャット2014年

6

神話#1:構造は軽量クラスである

この神話にはさまざまな形があります。一部の人々は、値型にメソッドやその他の重要な動作を含めることはできない、またはすべきでないと考えています。これらは、パブリックフィールドまたは単純なプロパティのみを持つ単純なデータ転送型として使用する必要があります。DateTimeタイプは、これに対する良い反例です。数値や文字などの基本単位であるという点で、値タイプであることが理にかなっており、また、その価値。逆の方向から見ると、データ転送のタイプはとにかく参照タイプである必要があります。決定は、タイプの単純さではなく、目的の値または参照タイプのセマンティクスに基づいている必要があります。他の人々は、パフォーマンスの点で、値のタイプは参照タイプよりも「軽い」と信じています。真実は、場合によっては値型の方がパフォーマンスが高いことです。たとえば、ボックス化されていない限り、ガベージコレクションが不要で、型識別のオーバーヘッドがなく、逆参照が必要ありません。ただし、他の点では、参照型の方がパフォーマンスが優れています。パラメーターの受け渡し、変数への値の割り当て、値の戻りなど、同様の操作でコピーする必要があるのは4バイトまたは8バイトのみです(32ビットCLRと64ビットCLRのどちらを実行しているかによって異なります)。 )すべてのデータをコピーするのではなく。ArrayListが何らかの形で「純粋な」値タイプであり、ArrayList式をメソッドに渡すと、そのすべてのデータをコピーする必要があると想像してください。とにかく、ほとんどすべての場合、パフォーマンスはこの種の決定によって実際に決定されるわけではありません。ボトルネックが予想される場所になることはほとんどなく、パフォーマンスに基づいて設計を決定する前に、さまざまなオプションを測定する必要があります。2つの信念の組み合わせも機能しないことは注目に値します。型が持つメソッドの数(クラスでも構造体でも)は関係ありません。インスタンスごとに使用されるメモリには影響しません。(コード自体に使用されるメモリの点でコストがかかりますが、それは各インスタンスではなく1回発生します。)

神話#2:参照のタイプはHEAPに存在する; スタックに生きる価値観のタイプ

これはしばしばそれを繰り返す人の部分の怠惰によって引き起こされます。最初の部分は正しいです。参照型のインスタンスは常にヒープ上に作成されます。問題を引き起こすのは2番目の部分です。すでに述べたように、変数の値は宣言された場所に存在するため、int型のインスタンス変数を持つクラスがある場合、特定のオブジェクトの変数の値は常に、オブジェクトの残りのデータの場所になります。ヒープ上。スタックには、ローカル変数(メソッド内で宣言された変数)とメソッドパラメーターのみが存在します。C#2以降では、第5章で匿名メソッドを見るとわかるように、一部のローカル変数は実際にはスタックに存在しません。これらの概念は今関連していますか?マネージコードを作成している場合は、メモリの最適な使用方法をランタイムに考慮させる必要があることは間違いありません。確かに、言語仕様は、何がどこにあるかについては保証しません。将来のランタイムは、それを回避できることがわかっている場合、スタック上にいくつかのオブジェクトを作成できる可能性があります。または、C#コンパイラがスタックをほとんど使用しないコードを生成する可能性があります。次の神話は通常、単に用語の問題です。

神話#3:オブジェクトはデフォルトでC#の参照によって渡される

これはおそらく最も広く普及している神話です。繰り返しますが、この主張をする人々は(常にではありませんが)C#の実際の動作を知っていますが、「参照渡し」が実際に何を意味するのかはわかりません。残念ながら、これが何を意味するかを知っている人にとっては、これは混乱を招きます。参照渡しの正式な定義は比較的複雑で、l値や同様のコンピューターサイエンス用語が含まれますが、重要なことは、参照渡しで変数を渡すと、呼び出しているメソッドが呼び出し元の変数の値を変更できるということです。パラメータ値を変更する。ここで、参照型変数の値は参照であり、オブジェクト自体ではないことに注意してください。パラメータ自体が参照によって渡されることなく、パラメータが参照するオブジェクトの内容を変更できます。例えば、

void AppendHello(StringBuilder builder)
{
    builder.Append("hello");
}

このメソッドが呼び出されると、パラメーター値(StringBuilderへの参照)が値で渡されます。メソッド内でビルダー変数の値を変更する場合(たとえば、ステートメントbuilder = null;を使用する場合)は、神話とは異なり、その変更は呼び出し元には表示されません。神話の「参照」ビットが不正確であるだけでなく、「オブジェクトが渡された」ビットも不正確であることに注意することは興味深いです。オブジェクト自体は、参照または値によって渡されることはありません。参照型が関係する場合、変数は参照によって渡されるか、引数(参照)の値は値によって渡されます。他のことは別にして、これはnullが値渡しの引数として使用されたときに何が起こるかという質問に答えます。オブジェクトが渡されている場合、渡すオブジェクトがないため問題が発生します。代わりに、null参照は、他の参照と同じ方法で値によって渡されます。この簡単な説明で困惑した場合は、私の記事「C#で渡されるパラメーター」(http://mng.bz/otVt)、より詳細に説明されています。これらの神話は周りの唯一のものではありません。ボクシングとアンボクシングは、誤解の公正な共有のためにやって来ました。次にそれを片付けようと思います。

参照: Jon SkeetによるC#in Depth 3rd Edition


1
あなたが正しいと仮定すると非常に良いです。参照を追加することも非常に良いです。
NoChance

5

良い最初の近似は「決して」ではないと思います。

良い2番目の近似は「決して」ではないと思います。

あなたがパフォーマンスに必死であるならば、それらを考慮してください、そして、その後常に測定してください。


24
私はその答えに同意しません。構造体は多くのシナリオで正当な用途があります。次に例を示します。プロセス間でデータをアトミックにマーシャリングします。
Franci Penov、2009

25
投稿を編集して、要点を詳しく説明する必要があります。あなたは自分の意見を述べましたが、この意見を採用する理由を裏付ける必要があります。
エリックフォーブス

4
構造体を使用するには、同等のTotin 'Chipカード(en.wikipedia.org/wiki/Totin%27_Chip)が必要だと思います。真剣に。
グレッグ

4
87.5Kの人はどのようにこのような回答を投稿しますか?彼は子供の頃それをしましたか?
Rohit Vipin Mathews、2015年

3
@Rohit-それは6年前でした。そのとき、サイトの基準は非常に異なっていました。これはまだ悪い答えですが、あなたは正しいです。
Andrew Arnold

5

私はWindows Communication Foundation [WCF] Named Pipeを扱っていましたが、データの交換が参照型ではなく値型であることを保証するために構造体を使用することが理にかなっていることに気付きました。


1
これが私にとって最良の手がかりです。
イヴァン

4

C#構造体は、クラスの軽量な代替手段です。クラスとほぼ同じように実行できますが、クラスではなく構造体を使用する方が「コストがかかりません」。この理由は少し技術的ですが、要約すると、クラスの新しいインスタンスがヒープに配置され、新しくインスタンス化された構造体がスタックに配置されます。さらに、クラスのように構造体への参照を扱うのではなく、構造体インスタンスを直接操作しています。これは、構造体を関数に渡す場合、参照としてではなく、値によるものであることも意味します。これについては、関数パラメーターに関する章で詳しく説明しています。

したがって、より単純なデータ構造を表現する場合、特にそれらの多くをインスタンス化することがわかっている場合は、構造体を使用する必要があります。.NETフレームワークには、Point、Rectangle、Color構造体など、クラスの代わりに構造体を使用する例がたくさんあります。


3

Structを使用して、ガベージコレクションのパフォーマンスを向上させることができます。通常、GCのパフォーマンスについて心配する必要はありませんが、GCがキラーになるシナリオがあります。低レイテンシアプリケーションの大きなキャッシュのように。例については、この投稿を参照してください。

http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/


3

構造または値のタイプは、次のシナリオで使用できます-

  1. オブジェクトがガベージコレクションによって収集されないようにする場合。
  2. 単純型で、メンバー関数がインスタンスフィールドを変更しない場合
  3. 他のタイプから派生したり、他のタイプに派生したりする必要がない場合。

値タイプと値タイプの詳細については、このリンクをご覧ください。


3

簡単に言うと、次の場合にstructを使用します。

1-オブジェクトのプロパティ/フィールドを変更する必要はありません。つまり、初期値を指定して、それらを読み取るだけです。

2-オブジェクトのプロパティとフィールドは値型であり、それほど大きくありません。

その場合は、構造体を利用してパフォーマンスを向上させ、メモリ割り当てを最適化できます。これらは、スタックとヒープ(クラス内)の両方ではなくスタックのみを使用するためです。


2

構造体を使用することはほとんどありません。しかし、それは私だけです。オブジェクトをnullにできるようにする必要があるかどうかによって異なります。

他の回答で述べたように、私は実際のオブジェクトのクラスを使用しています。また、構造体は少量のデータを格納するために使用されるという考え方もあります。


-11

構造体は、ほとんどの場合、クラス/オブジェクトのようなものです。構造には関数、メンバーを含めることができ、継承することができます。ただし、構造はC#であり、データの保持にのみ使用されます。構造体はクラスよりもRAMの使用が少なく、ガベージコレクターによる収集簡単です。ただし、構造体で関数を使用する場合、コンパイラは実際にはクラス/オブジェクトと非常によく似た構造をとるので、関数で何かが必要な場合は、クラス/オブジェクトを使用します


2
構造は継承できません
。msdn.microsoft.com/ en
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.