問題

C#でstructとnot classをいつ使うべきですか?私の概念モデルは、アイテムが単なる値型のコレクションであるときに構造体が使用されることです。論理的にそれらをすべて一緒にcohesive全体にまとめる方法。

私はこれらのルールここでに出くわしました:

  • 構造体は単一を表す必要があります 値を返します。
  • 構造体にはメモリが必要です フットプリントは 16 バイト未満です。
  • 構造体は、 創造だ

これらのルールは機能しますか?構造体は何を意味しますか?

  ベストアンサー

OPによって参照されるソースにはいくつかの信頼性があります...しかし、Microsoftはどうですか?構造体の使用状況は何ですか?私はMicrosoft からいくつかの余分なを学び、ここに私が見つけたものがあります:

クラスのインスタンスが 型は小さく、一般的に短命であるか、 その他の物。

型が以下のすべての特徴を持っていない限り、構造体を定義しないでください。

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

Microsoftは一貫してこれらの規則に違反しています

さて、#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はおそらく問題ではないので、かなり忘れています。あなたが構造体をボクシングしているのを見つけたら、あなたのアーキテクチャを再考してください。

Microsoftがこれらの構造体を使用する理由を見てみましょう:

  1. 各構造体 Entry および Enumerator は単一の値を表します。
  2. スピード
  3. EntryはDictionaryクラスの外でパラメータとして渡されることはありません。さらなる調査では、IEnumerableの実装を満たすために、DictionaryはEnumerator構造体を使用しています。
  4. Dictionary は IEnumerator インタフェースの実装と等しいアクセシビリティを持つ必要があるため、 Enumerator は public です。 IEnumerator getter 。

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

ここでは、構造体を不変に保つか、16バイト以下のインスタンスサイズを維持する必要があるという試みや証明があります。

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

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

ハッシュされたキーにかかわらず、構造体のインスタンス化が参照型よりも速いため、辞書は部分的に高速です。ここでは、順番に増分されたキーを持つ300,000個のランダムな整数を格納するDictionary<int, int>があります。

容量:312874
または MEMSIZE:2660827バイト
または 完了したリサイズ:5ms
または 塗りつぶす合計時間:889ms

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

MEMSIZE:辞書をMemoryStreamにシリアル化し、バイト長を取得することによって決定します(私たちの目的には十分正確です)。

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

記入する合計時間:ロギングとソースに追加したOnResizeイベントのために確かにスキーされました。しかし、操作中に15回サイズ変更しながら300kの整数を埋めるのにはまだ印象的です。好奇心のために、すでに容量を知っていれば合計時間はどのようになりますか? 13ms

ですから、Entryがクラスであった場合はどうなりますか?これらの時間や指標は本当にそれほど違いますか?

容量:312874
または MEMSIZE:2660827バイト
または 完了したリサイズ:26ms
または 塗りつぶす合計時間:964ms

明らかに、大きな違いはサイズ変更にあります。 DictionaryがCapacityで初期化されている場合の違いはありますか? ... 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で見つけることができます:構造設計。要するに:

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

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

C#コンパイラなどの一部のコンパイラは、構造体を許可しません デフォルトのコンストラクタを持っています。

実際には非常にシンプルで、 AsimovのRobotics の3つの法則から借ります:

  1. 構造体は安全でなければなりません
  2. これがルール#1に違反しない限り、構造体はその関数を効率的に実行する必要があります。
  3. ルール#1を満たすために破壊が必要でない限り、構造体はそのまま残っておく必要があります。

...私たちはこれから何を取り除くのですか?要するに、値型の使用に責任があります。彼らは素早く効率的ですが、適切に維持されていない場合(つまり意図しないコピー)、多くの予期しない動作を引き起こす能力があります。

  同じタグがついた質問を見る

c#struct