C#でクラスではなく構造体を使用する必要があるのはいつですか?私の概念モデルでは、構造体は、項目が単なる値型のコレクションであるときに使用されます。それらを論理的にまとめて、まとまりのある全体にする方法。
- 構造体は単一の値を表す必要があります。
- 構造体のメモリフットプリントは16バイト未満である必要があります。
- 構造体は作成後に変更しないでください。
これらのルールは機能しますか?構造体は意味的にどういう意味ですか?
C#でクラスではなく構造体を使用する必要があるのはいつですか?私の概念モデルでは、構造体は、項目が単なる値型のコレクションであるときに使用されます。それらを論理的にまとめて、まとまりのある全体にする方法。
これらのルールは機能しますか?構造体は意味的にどういう意味ですか?
回答:
OPが参照するソースにはある程度の信頼性がありますが、Microsoftについてはどうですか-構造体の使用に関するスタンスは何ですか?私はマイクロソフトに追加の知識を求めました、そしてここに私が見つけたものがあります:
タイプのインスタンスが小さく、一般的に存続期間が短いか、一般的に他のオブジェクトに埋め込まれている場合は、クラスの代わりに構造を定義することを検討してください。
タイプに次のすべての特性がない限り、構造を定義しないでください。
- プリミティブ型(整数、ダブルなど)と同様に、論理的に単一の値を表します。
- インスタンスサイズは16バイト未満です。
- それは不変です。
- 頻繁にボックス化する必要はありません。
とにかく、#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はおそらく問題ではないので、かなり許されます。構造体をボクシングしている場合は、アーキテクチャを考え直してください。
マイクロソフトがこれらの構造体を使用する理由を見てみましょう。
Entry
およびEnumerator
、単一の値を表します。Entry
辞書クラスの外でパラメーターとして渡されることはありません。さらなる調査により、IEnumerableの実装を満たすために、Dictionaryは、Enumerator
列挙子が要求されるたびにコピーする構造体を使用することがわかります。Enumerator
ディクショナリは列挙可能であり、IEnumeratorインターフェイス実装(IEnumeratorゲッターなど)への同等のアクセシビリティを持つ必要があるため、パブリックです。 更新 -さらに、構造体がEnumeratorと同様にインターフェイスを実装し、その実装された型にキャストされると、構造体は参照型になり、ヒープに移動されることを理解してください。Enumerator は、Dictionaryクラスの内部でも値型です。ただし、メソッドがを呼び出すとすぐGetEnumerator()
に、参照タイプIEnumerator
が返されます。
ここに表示されていないのは、構造体を不変に保つ、またはインスタンスサイズを16バイト以下に維持するための試みまたは要件の証明です。
readonly
- 不変ではありません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つの法則から借ります:
... これから何を取り除きますか?つまり、値型の使用に責任があります。これらは迅速かつ効率的ですが、適切にメンテナンスされていないと、予期しない動作(つまり、意図しないコピー)を引き起こす可能性があります。
Decimal
またはDateTime
]のように単一のエンティティを表す場合、他の3つのルールを遵守しない場合は、クラスで置き換える必要があります。構造が変数の固定コレクションを保持し、それぞれがそのタイプに有効な任意の値を保持する可能性がある場合(例Rectangle
:)、異なる規則に従う必要があります。その一部は「単一値」構造の規則に反しています。 。
Dictionary
エントリタイプが内部タイプのみであること、パフォーマンスがセマンティクスより重要であると考えられていること、またはその他の言い訳に基づいてエントリタイプを正当化します。私のポイントは以下のようなタイプがあることでRectangle
、パフォーマンス上の利点は、結果の意味的な欠陥を上回る「ので」、その内容を個別に編集可能なフィールドではないとして公開されている必要がありますが、ため、タイプは意味的に独立した値の固定セットを表し、そして可変構造体の両方があるので、よりパフォーマンスが高く、意味的に優れています。
いつでも:
ただし、注意点は、構造体(任意に大きい)はクラス参照(通常は1つのマシンワード)よりも渡すのにコストがかかるため、実際にはクラスが高速になる可能性があるということです。
元の投稿に記載されているルールに同意しません。ここに私のルールがあります:
1)配列に格納する場合、パフォーマンスのために構造体を使用します。(構造体がいつ解答するのか参照してください。)
2)C / C ++との間で構造化データを渡すコードでそれらが必要です。
3)必要な場合を除き、構造体は使用しないでください。
struct
それがどのように動作するかを知るためにあることを知っている必要があるのは本当ですが、何かがstruct
公開されたフィールドを持つものである場合、それはすべて知っておく必要があります。オブジェクトがexposed-field-struct型のプロパティを公開し、コードがその構造体を変数に読み取って変更した場合、そのようなアクションは、構造体が書き込まれるまで、または書き込まれるまで、プロパティが読み取られたオブジェクトに影響を与えないと予測できます。バック。対照的に、プロパティが変更可能なクラスタイプである場合、それを読み取って変更すると、基になるオブジェクトが期待どおりに更新される可能性がありますが...
参照セマンティクスではなく値セマンティクスが必要な場合は、構造体を使用します。
なぜ人々がこれに反対票を投じているのかはわかりませんが、これは妥当な点であり、オペレーションが彼の質問を明確にする前に行われました、そしてそれは構造の最も基本的な基本的な理由です。
参照セマンティクスが必要な場合は、構造体ではなくクラスが必要です。
「それは価値がある」という回答に加えて、構造体を使用する特定のシナリオの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]);
繰り返しますが、これはインプレースです。値をコピーしていません。
非常に具体的なシナリオでは、この戦術は非常に成功する可能性があります。ただし、これはかなり高度なシナリオであり、自分が何をしているか、そしてその理由を知っている場合にのみ試行する必要があります。ここでのデフォルトはクラスです。
List
私は、Array
背後のシーンを使用すると思います。番号 ?
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パラメータを除いて、構造体への参照を作成することはできません。これにより、多くの状況での使用が禁止されます。
ref
を変更可能な構造体に安全に与えて、外部メソッドがその構造体に対して実行するすべての変更は、それが戻る前に行われることを知っています。
ref
た構造体の有利なセマンティクスをクラスオブジェクトで実現できます。基本的に、ローカル変数、パラメーター、および関数の戻り値は、永続可能(デフォルト)、戻り可能、または一時的である可能性があります。コードは、一時的なものを現在の範囲を超えるものにコピーすることを禁止されます。返却可能なものは、関数から返される可能性があることを除いて、一時的なものと同じです。関数の戻り値は、その「戻り可能」パラメーターのいずれにも適用できる最も厳しい制限に拘束されます。
ここに基本的なルールがあります。
すべてのメンバーフィールドが値型の場合は、構造体を作成します。
いずれかのメンバーフィールドが参照型の場合は、クラスを作成します。これは、参照タイプのフィールドにはとにかくヒープの割り当てが必要になるためです。
例
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
}
string
は、意味的に値と同等であり、不変オブジェクトへの参照をフィールドに格納しても、ヒープ割り当ては必要ありません。露出されたパブリックフィールドを持つ構造体と露出パブリックフィールドを持つクラスのオブジェクトとの間の差は、コード配列与えつまりvar q=p; p.X=4; q.X=5;
、p.X
場合に値4を持つことになりa
、それがクラス型かどう構造型であり、5。1つの願いが便利なタイプのメンバーを変更することができるようにする場合は、1は1つがに変更望んでいるかどうかに基づいて「クラス」や「構造体」を選択する必要がありますq
影響を与えるがp
。
ArraySegment<T>
カプセル化しT[]
ます。構造体型KeyValuePair<TKey,TValue>
は、一般的なパラメーターとしてクラス型と共に使用されることがよくあります。
StructLayoutAttribute(通常はPInvokeの場合)を使用してメモリレイアウトを明示的に指定する場合は、「構造体」を使用する必要があります。
編集:コメントは、クラスまたは構造体をStructLayoutAttributeで使用できることを指摘しています。これは確かに当てはまります。実際には、通常、構造体を使用します-構造体はスタックとヒープで割り当てられます。これは、アンマネージメソッドの呼び出しに引数を渡すだけの場合に意味があります。
私は、あらゆる種類のバイナリ通信形式をパックまたはアンパックするために構造体を使用しています。これには、ディスクの読み取りまたは書き込み、DirectX頂点リスト、ネットワークプロトコル、または暗号化/圧縮されたデータの処理が含まれます。
あなたが挙げた3つのガイドラインは、この文脈では私にとって役に立たなかった。400バイトのものを特定の順序で書き出す必要がある場合は、400バイトの構造体を定義し、それに関係のないすべての値を入れて、その時点で最も理にかなった方法で設定します。(さて、400バイトはかなり奇妙です-しかし、私が生活のためにExcelファイルを書き込んでいたとき、私はすべて約40バイトまでの構造体を扱っていました、それはそれがBIFFレコードのいくつかがどれほど大きいかであるからです。)
PInvokeの目的でランタイムやその他のさまざまなものによって直接使用されるvaluetypeを除いて、valuetypeは2つのシナリオでのみ使用する必要があります。
this
メソッドの呼び出しに使用される一時パラメーターを除く)。クラスを使用すると、参照を複製できます。
.NETはvalue types
およびをサポートしますreference types
(Javaでは、参照型のみを定義できます)。のインスタンスはreference types
マネージヒープに割り当てられ、インスタンスへの未解決の参照がない場合はガベージコレクションされます。value types
一方、のインスタンスはに割り当てられるstack
ため、割り当てられたメモリはスコープが終了するとすぐに再利用されます。そしてもちろん、value types
値とreference types
参照によって渡されます。System.Stringを除くすべてのC#プリミティブデータ型は値型です。
クラスで構造体を使用する場合、
C#では、structs
あるvalue types
、クラスがありますreference types
。enum
キーワードとキーワードを使用して、C#で値タイプを作成できますstruct
。aのvalue type
代わりにを使用するreference type
と、マネージヒープ上のオブジェクトが少なくなり、ガベージコレクター(GC)への負荷が減り、GCサイクルの頻度が減り、結果的にパフォーマンスが向上します。ただし、value types
欠点もあります。大きなstruct
ものを渡すことは、リファレンスを渡すよりも明らかにコストがかかります。これは明らかな問題の1つです。もう1つの問題は、に関連するオーバーヘッドboxing/unboxing
です。どういうboxing/unboxing
意味かわからない場合は、これらのリンクをたどってboxing
、unboxing
。パフォーマンスとは別に、型が値のセマンティクスを持つ必要があるだけの場合reference types
あなたが持っているすべてです。value types
コピーのセマンティクスが必要な場合、または通常arrays
これらのタイプの自動初期化が必要な場合にのみ使用してください。
ref
です。任意のサイズの構造体ref
を渡すことは、値によってクラス参照を渡すことと同じコストです。サイズ構造をコピーしたり、値で渡したりする方が、クラスオブジェクトの防御コピーを実行してそれへの参照を保存または渡すよりも安価です。大きな時間のクラスがある(1)クラスは(守備のコピーを避けるためになるように)不変であり、作成される各インスタンスはたくさんの周りに渡される、または...の値を格納するための構造体よりも優れている
readOnlyStruct.someMember = 5;
はsomeMember
、読み取り専用プロパティを作成することではなく、フィールドにすることです。
構造体は値型です。構造体を新しい変数に割り当てると、新しい変数には元の変数のコピーが含まれます。
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
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;
}
}
C#または他の.net言語の構造体型は、通常、固定サイズの値のグループのように動作する必要があるものを保持するために使用されます。構造タイプの有用な側面は、構造タイプインスタンスのフィールドは、それが保持されているストレージの場所を変更することによってのみ変更できることです。フィールドを変更する唯一の方法はまったく新しいインスタンスを作成し、構造体の割り当てを使用して新しいインスタンスの値でフィールドを上書きすることでターゲットのすべてのフィールドを変更するように構造をコーディングすることは可能ですが、構造体がフィールドにデフォルト値以外のインスタンスを作成する手段を提供しない限り、構造体自体が変更可能な場所に格納されている場合、そのすべてのフィールドは変更可能になります。
構造体にプライベートクラスタイプフィールドが含まれ、その独自のメンバーをラップされたクラスオブジェクトのメンバーにリダイレクトする場合、構造体タイプを本質的にクラスタイプのように動作するように設計できることに注意してください。たとえば、a PersonCollection
はプロパティSortedByName
とを提供しSortedById
、どちらもPersonCollection
(コンストラクタで設定された)への「不変」参照を保持GetEnumerator
てを呼び出すことで実装しcreator.GetNameSortedEnumerator
またはのかをcreator.GetIdSortedEnumerator
。そのような構造体はPersonCollection
、それらのGetEnumerator
メソッドがの異なるメソッドにバインドされることを除いて、への参照とほとんど同じように動作しPersonCollection
ます。配列の一部をラップする構造を持つこともできます(たとえばArrayRange<T>
、T[]
呼び出されArr
たintを保持する構造を定義することもできます)Offset
、およびLength
、インデックス付きプロパティを使用して、idx
0からの範囲のインデックスに対してLength-1
アクセスしますArr[idx+Offset]
)。残念ながら、foo
がそのような構造の読み取り専用インスタンスである場合、現在のコンパイラバージョンでは、foo[3]+=4;
そのような操作がのフィールドに書き込もうとするかどうかを判断する方法がないため、このような操作は許可されませんfoo
。
可変サイズのコレクション(構造体がコピーされるたびにコピーされるように見える)を保持する値型のように動作するように構造を設計することもできますが、その動作を行う唯一の方法は、構造体は参照を保持し、それを変更する可能性のあるものに公開されます。たとえば、プライベート配列を保持する配列のような構造体があり、そのインデックス付きの「put」メソッドは、1つの変更された要素を除いて、内容が元の配列と同じである新しい配列を作成します。残念ながら、そのような構造体を効率的に実行させることは多少難しい場合があります。構造体のセマンティクスが便利な場合があります(たとえば、配列のようなコレクションをルーチンに渡すことができ、呼び出し元と呼び出し先の両方が外部コードがコレクションを変更しないことを知っている場合)
いや-私は完全にルールに同意しません。これらは、パフォーマンスと標準化について検討するのに適したガイドラインですが、可能性を考慮したものではありません。
応答でわかるように、それらを使用する多くの創造的な方法があります。したがって、これらのガイドラインは、パフォーマンスと効率のために常にそうである必要があります。
この場合、クラスを使用して実世界のオブジェクトをより大きな形式で表し、構造体を使用してより正確な用途を持つ小さなオブジェクトを表します。あなたがそれを言った方法、「よりまとまりのある全体」。キーワードはまとまりがあります。クラスはよりオブジェクト指向の要素になりますが、構造体はこれらの特性の一部を持つことができますが、規模は小さくなります。IMO。
一般的な静的属性にすばやくアクセスできるTreeviewおよびListviewタグでこれらを頻繁に使用します。私はいつもこの情報を別の方法で取得するのに苦労してきました。たとえば、私のデータベースアプリケーションでは、テーブル、SP、関数、またはその他のオブジェクトがあるツリービューを使用しています。構造体を作成してデータを入力し、タグに挿入して引き出し、選択範囲のデータを取得します。私はこれをクラスではしません!
私はそれらを小さくして、単一インスタンスの状況で使用し、変更されないようにしています。メモリ、割り当て、パフォーマンスに注意することは賢明です。そしてテストはとても必要です。
double
示し、これらの座標の値の任意の組み合わせでインスタンスを作成することが可能である場合、そのような仕様はそれを強制します。マルチスレッドの動作の一部の詳細を除いて、公開フィールド構造と意味的に同じように動作します(不変クラスの方が優れている場合もあれば、公開フィールド構造の方が優れている場合もあります。いわゆる「不変」構造はすべてのケースで悪化する)。
私のルールは
1、常にクラスを使用します。
2、パフォーマンスの問題がある場合は、@ IAbstractが言及したルールに応じて、いくつかのクラスをstructに変更し、これらの変更によってパフォーマンスが向上するかどうかをテストします。
Foo
独立した値の固定されたコレクション(たとえば、点の座標)をカプセル化させたい場合であり、グループとして渡したい場合や独立して変更したい場合があります。両方の目的を単純な公開フィールド構造体(独立変数の固定されたコレクションであり、法案に完全に適合します)とほぼ同じように組み合わせるクラスを使用するためのパターンは見つかりませんでした。
public readonly
、自分のタイプのフィールドを公開し始めました。なぜなら、読み取り専用プロパティを作成することは、実際には何の利益にもならないだけの作業です。)
MyListOfPoint[3].Offset(2,3);
に由来しvar temp=MyListOfPoint[3]; temp.Offset(2,3);
ます。これは、適用すると偽の変換になります...
Offset
メソッドに。このような偽のコードを防ぐ適切な方法は、構造体を不必要に不変にすることではなくOffset
、前述の変換を禁止する属性でタグ付けできるようなメソッドを許可することです。暗黙的な数値変換も、呼び出しが明らかな場合にのみ適用できるようにタグ付けできれば、はるかに優れていた可能性があります。foo(float,float)
and foo(double,double)
にオーバーロードが存在する場合、a float
とa を使用しようとするとdouble
、暗黙の変換は適用されず、エラーになるはずです。
double
値を直接割り当てるfloat
か、またはfloat
引数をとることはできるが、引数をとることができないメソッドに値を渡すとdouble
、ほとんどの場合、プログラマの意図したとおりに動作します。対照的に、明示的な型キャストなしでfloat
式をに割り当てることは、double
多くの場合誤りです。暗黙的なdouble->float
変換を許可するのは、問題が発生するのは、理想的ではないオーバーロードが選択されるときだけです。それを防ぐための正しい方法は、暗黙のdouble-> floatを禁止するべきではなく、変換を許可しない属性でオーバーロードにタグを付けることでした。
クラスは参照型です。クラスのオブジェクトが作成されると、オブジェクトが割り当てられている変数は、そのメモリへの参照のみを保持します。オブジェクト参照が新しい変数に割り当てられると、新しい変数は元のオブジェクトを参照します。1つの変数を介して行われた変更は、両方とも同じデータを参照するため、他の変数に反映されます。構造体は値型です。構造体が作成されると、構造体が割り当てられている変数は、構造体の実際のデータを保持します。構造体が新しい変数に割り当てられると、それがコピーされます。したがって、新しい変数と元の変数には、同じデータの2つの別々のコピーが含まれています。1つのコピーに加えられた変更は、他のコピーには影響しません。一般に、クラスは、より複雑な動作、またはクラスオブジェクトの作成後に変更することを目的としたデータをモデル化するために使用されます。
神話#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
良い最初の近似は「決して」ではないと思います。
良い2番目の近似は「決して」ではないと思います。
あなたがパフォーマンスに必死であるならば、それらを考慮してください、そして、その後常に測定してください。
C#構造体は、クラスの軽量な代替手段です。クラスとほぼ同じように実行できますが、クラスではなく構造体を使用する方が「コストがかかりません」。この理由は少し技術的ですが、要約すると、クラスの新しいインスタンスがヒープに配置され、新しくインスタンス化された構造体がスタックに配置されます。さらに、クラスのように構造体への参照を扱うのではなく、構造体インスタンスを直接操作しています。これは、構造体を関数に渡す場合、参照としてではなく、値によるものであることも意味します。これについては、関数パラメーターに関する章で詳しく説明しています。
したがって、より単純なデータ構造を表現する場合、特にそれらの多くをインスタンス化することがわかっている場合は、構造体を使用する必要があります。.NETフレームワークには、Point、Rectangle、Color構造体など、クラスの代わりに構造体を使用する例がたくさんあります。
Structを使用して、ガベージコレクションのパフォーマンスを向上させることができます。通常、GCのパフォーマンスについて心配する必要はありませんが、GCがキラーになるシナリオがあります。低レイテンシアプリケーションの大きなキャッシュのように。例については、この投稿を参照してください。
http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/
構造または値のタイプは、次のシナリオで使用できます-
値タイプと値タイプの詳細については、このリンクをご覧ください。
構造体は、ほとんどの場合、クラス/オブジェクトのようなものです。構造には関数、メンバーを含めることができ、継承することができます。ただし、構造はC#であり、データの保持にのみ使用されます。構造体はクラスよりもRAMの使用量が少なく、ガベージコレクターによる収集が簡単です。ただし、構造体で関数を使用する場合、コンパイラは実際にはクラス/オブジェクトと非常によく似た構造をとるので、関数で何かが必要な場合は、クラス/オブジェクトを使用します。
System.Drawing.Rectangle
これら3つのルールすべてに違反しています。