web-dev-qa-db-ja.com

いつstructを使うの?

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

私はこれらの規則に出くわした ここ

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

これらの規則は機能しますか?構造体とはどういう意味ですか?

1298
Alex Baranosky

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

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

ここで見ていないのは、構造体を不変にしたり、インスタンスサイズをわずか16バイト以下に維持しようとするあらゆる試みまたは要件の証明です。

  1. 上記の構造体には何も宣言されていませんreadonly - not immutable
  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バイト
リサイズ完了:26ミリ秒
いっぱいになるまでの合計時間: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:Structure Design にあります。要するに:

構造体のデフォルトコンストラクタを提供しません。

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

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

それは実際には非常に単純で、私たちは AsimovのThree Laws of Robotics /から借りるでしょう。

  1. 構造体は安全に使用できなければなりません
  2. これが規則#1に違反しない限り、構造体はその機能を効率的に実行しなければなりません。
  3. 破壊が規則#1を満たすのに必要とされない限り、構造体はその使用中に無傷のままでなければなりません

...これから何を取り除きますか:要するに、値型の使用に責任があります。それらは迅速かつ効率的であるが、適切に維持されなければ(すなわち、意図しないコピー)多くの予想外の行動を引き起こす能力を有する。

577
IAbstract

多態性を必要としないときはいつでも、値の意味論がほしいと思い、ヒープ割り当ておよび関連するガベージコレクションのオーバーヘッドを避けたいと思う。ただし、構造体(任意の大きさ)の方がクラス参照(通常は1マシンワード)よりもコストがかかるため、実際にはクラスの速度が上がる可能性があります。

151
dsimcha

私は最初の投稿で与えられた規則に同意しません。これが私のルールです:

1)配列に格納されるとき、パフォーマンスのために構造体を使います。 ( も参照。構造体はいつ答えになるのか?

2)C/C++との間で構造化データをやり取りするコードで必要です。

3)あなたがそれらを必要としない限り構造体を使用しないでください。

  • それらは、代入時や引数として渡したときの "通常のオブジェクト"( 参照型 )とは異なる動作をします。これは予期しない動作を引き起こす可能性があります。コードを見ている人が構造体を扱っていることを知らない場合、これは特に危険です。
  • 継承することはできません。
  • 引数として構造体を渡すことは、クラスよりもコストがかかります。
141
ILoveFortran

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

編集する

なぜ人々がこれを軽視しているのかは定かではありませんが、これは妥当な点です。

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

83
JoshBerke

"それは値です"という回答に加えて、構造体を使用するための具体的なシナリオの1つは、knowで、ガベージコレクションの問題を引き起こしている一連のデータがあるときオブジェクトの。たとえば、Personインスタンスの大規模なリスト/配列などです。ここでの自然な比喩はクラスですが、もしあなたが長命のPersonインスタンスをたくさん持っていると、それらはGEN-2を詰まらせてGCストールを引き起こすことになるかもしれません。シナリオで保証されている場合、ここで考えられる1つのアプローチはPerson structsの配列(リストではない)、つまりPerson[]を使用することです。さて、GEN-2には何百万ものオブジェクトがあるのではなく、LOHに1つのチャンクがあります(ここでは文字列などはないと仮定します。つまり、参照のない純粋な値です)。これによるGCへの影響はほとんどありません。

データはおそらく構造体には大きすぎるので、このデータを使って作業するのは面倒です。また、常にfat値をコピーしたくはありません。ただし、配列内で直接アクセスしても構造体はコピーされません。インプレースです(コピーするリストインデクサとは対照的です)。これはインデックスを使った多くの作業を意味します。

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

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

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

繰り返しますが、これは適切なものです - 値はコピーされていません。

非常に特殊なシナリオでは、この戦略は非常に成功する可能性があります。しかし、それはあなたがあなたが何をしているのか、そしてその理由を知っている場合にのみ試みられるべきであるかなり高度な計画です。ここでのデフォルトはクラスです。

59
Marc Gravell

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

別の方法はPointを構造体にすることです。

struct Point
{
   public int x, y;

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

これで、インスタンス化されたオブジェクトが1つだけ(配列用のもの)、Pointインスタンスが配列内にインラインで格納されました。

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

クラスでは、2つの変数が同じオブジェクトを参照する可能性があるため、一方の変数に対する操作が他方の変数によって参照されるオブジェクトに影響を与える可能性があります。構造体では、変数はそれぞれ独自のデータのコピーを持ち、一方の操作が他方の操作に影響を与えることはありません。たとえば、次のコードで生成される出力は、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になります。このコピーは、その後のa.xへの代入による影響を受けません。

前の例は、構造体の2つの制限を強調しています。まず、構造体全体をコピーする方が、オブジェクト参照をコピーするよりも効率的ではないため、割り当てや値パラメータの受け渡しは、参照型よりも構造体のほうがコストがかかる可能性があります。次に、refおよびoutパラメータを除いて、構造体への参照を作成することはできません。これは、さまざまな状況での使用を除外します。

39
bUKaneer

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

33
Franci Penov

これが基本的な規則です。

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

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

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
}
25
Usman Zafar

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

秒:とにかくデータが参照ポインタとほぼ同じサイズであるとき。

18
BC.

通常PInvokeに StructLayoutAttribute - を使用して明示的にメモリレイアウトを指定したい場合は、 "struct"を使用する必要があります。

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

17

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

あなたがリストした3つのガイドラインは、この文脈では私にとって役に立ちませんでした。ある特定の順序で400バイトのものを書き出す必要があるときは、400バイトの構造体を定義するつもりです、そしてそれが持つべきであろう無関係な値でそれを満たすつもりです、そして私は行きますそのときに最も理にかなっている方法で設定すること。 (さて、400バイトはかなり奇妙なものになるでしょう。でも、私がExcelファイルを書いていたとき、BIFFレコードの中にはかなり大きいものがあるので、最大40バイトまでの構造体を扱っていました。)

16
mjfgates

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

  1. コピーセマンティクスが必要なとき.
  2. 自動初期化が必要なときは、通常これらの型の配列で。
15
leppie

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

クラスに対してstructを使用する場合、

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

14
Sujit

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

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.

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

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

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
10
Jason Williams

C#や他の.NET言語の構造体型は、通常、固定サイズの値のグループのように振る舞うべきものを保持するために使用されます。構造体型の便利な点は、構造体型インスタンスのフィールドは、それが保持されている格納場所を変更することによって変更できることです。フィールドを変更する唯一の方法は新しいインスタンス全体を作成し、それから新しいインスタンスからの値でそれらを上書きすることによってターゲットのすべてのフィールドを変更するために構造体代入を使用することです。構造体がそのフィールドがデフォルト以外の値を持つインスタンスを作成する手段を提供していない限り、その構造体自体が可変位置に格納されていれば、そのすべてのフィールドは可変になります。

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

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

10
supercat

Nah - 私は完全に規則に同意しません。それらはパフォーマンスと標準化を考慮して考慮すべき良いガイドラインですが、可能性を考慮していません。

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

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

私はそれらをTreeviewとListviewタグでよく使用します。そこでは共通の静的属性に非常に素早くアクセスすることができます。私はいつも別の方法でこの情報を入手するのに苦労してきました。たとえば、私のデータベースアプリケーションでは、テーブル、SP、関数、その他のオブジェクトがあるツリービューを使用します。私は自分の構造体を作成してデータを追加し、それをタグに入れ、引き出し、選択したデータを取得するなどします。私はクラスでこれをしないでしょう!

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

9
SnapJag

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

したがって結論は次のとおりです。LINQおよび隠し構造体のボックス化/ボックス化解除に注意し、マイクロ最適化のために構造体を使用する場合は厳密に配列を使用してください。

P.S呼び出しスタックを通して構造体/クラスを渡すことに関するもう一つのベンチマークがあります 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;
        }
    }
9

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

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

8
J_hajian_nzd

私のルールは

1、常にクラスを使う。

2、パフォーマンス上の問題がある場合は、@ IAbstractの規則に従ってクラスをstructに変更し、これらの変更によってパフォーマンスが向上するかどうかをテストします。

8
rockXrock

私はちょうどWindows Communication Foundation [WCF]名前付きパイプを扱っていた、そしてそれはデータ交換が 参照型 の代わりに 値型 であることを保証するためにStructsを使う意味があることに気づいた。

5
N_E

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

ですから、もっと単純なデータ構造を表現したいとき、そしてあなたがそれらの多くをインスタンス化しようとしていることを知っているなら、あなたは構造体を使うべきです。 .NETフレームワークには、Microsoftがクラスの代わりに構造体、たとえばPoint、Rectangle、およびColor構造体を使用している例が多数あります。

4
Saeed Dini

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

私は良い二次近似は「しない」と思います。

あなたがperfを切望しているならば、それらを考慮しなさい、しかしそれから常に測定しなさい。

4
Brian

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

1 - あなたのオブジェクトのプロパティ/フィールドを変更する必要はありません。初期値を与えて読みたいだけなのです。

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

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

3
akazemis

構造体は、ガベージコレクションのパフォーマンスを向上させるために使用できます。通常、GCのパフォーマンスについて心配する必要はありませんが、それが致命的になる可能性があるシナリオがあります。低レイテンシアプリケーションの大規模キャッシュのように。例としてこの記事を参照してください。

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

3
Rabbit

構造型または値型は、以下のシナリオで使用できます -

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

値の型と値についてもっと知ることができます このリンクにある型はここにあります

3
Vikram

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

この神話はさまざまな形で現れます。値型はメソッドやその他の重要な動作を持つことはできない、または持つべきではないと考える人もいます。パブリックフィールドや単純なプロパティだけで、単純なデータ転送型として使用する必要があります。 DateTime型は、これに対する良い反例です。数値や文字のような基本単位であるという意味で、値型であることは意味があります。また、に基づいて計算を実行できることも意味があります。その価値別の方向から見れば、データ転送型はいずれにせよ参照型であるべきです - 決定は型の単純さではなく、望ましい値または参照型セマンティクスに基づいているべきです。他の人々は、パフォーマンスの観点から、バリュータイプは参照タイプよりも「軽い」と考えています。実のところ、場合によっては値型のほうがパフォーマンスが優れています。たとえば、値が入力されている場合を除き、ガベージコレクションを必要とせず、型識別のオーバーヘッドもなく、参照解除も必要ありません。ただし、他の方法では、参照型のほうがパフォーマンスがよくなります。パラメータの受け渡し、変数への値の代入、戻り値などの操作では、4または8バイトをコピーするだけで済みます(32ビットまたは64ビットCLRを実行しているかによって異なります)すべてのデータをコピーするのではなく) ArrayListがなんらかの理由で「純粋な」値型で、メソッドにArrayList式を渡すと、そのすべてのデータがコピーされることを想像してみてください。ほとんどの場合、パフォーマンスはこのような決定によって決定されるわけではありません。ボトルネックは、自分が考えているところではほとんどありません。パフォーマンスに基づいて設計を決定する前に、さまざまな選択肢を検討する必要があります。 2つの信念の組み合わせがうまくいかないことは注目に値します。 1つの型がいくつのメソッドを持っていても(それがクラスであれ構造体であれ)、インスタンスごとに消費されるメモリは影響を受けません。 (コード自体のためにメモリを消費するという点でコストがかかりますが、インスタンスごとではなく1回発生します)。

神話#2:参考文献の種類値の種類はスタック上でライブ

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

神話#3:目的はC#の参照によってデフォルトで渡されている

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

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

このメソッドが呼び出されると、パラメータ値(StringBuilderへの参照)が値渡しされます。メソッド内でBuilder変数の値を変更する場合(例えば、builder = null;というステートメント)は、神話とは異なり、呼び出し側にはその変更が反映されません。興味深いのは、神話の「参照による」ビットが不正確であるだけでなく、「オブジェクトが渡される」ビットも不正確であるということです。オブジェクト自体は、参照または値によって渡されることはありません。参照型が関係しているときは、変数は参照によって渡されるか、引数の値(参照)は値によって渡されます。他のことは別として、これはnullが値による引数として使用された場合に何が起こるのかという疑問に答えます - オブジェクトが渡されている場合、それは渡すオブジェクトがないので問題を引き起こします!代わりに、null参照は他の参照と同じ方法で値によって渡されます。この素早い説明であなたが当惑してしまった場合は、私の記事「C#でのパラメータ渡し」( http://mng.bz/otVt )を見てください。これらの神話が唯一のものではありません。ボクシングとボクシング解除は、誤解のかなりの部分を占めています。これについては、次に説明します。

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

1
habib