.NETの構造体とクラスの違いは何ですか?
19 に答える
In .NET, there are two categories of types, reference types and value types.
Structs are value types and classes are reference types.
The general difference is that a reference type lives on the heap, and a value type lives inline, that is, wherever it is your variable or field is defined.
A variable containing a value type contains the entire value type value. For a struct, that means that the variable contains the entire struct, with all its fields.
A variable containing a reference type contains a pointer, or a reference to somewhere else in memory where the actual value resides.
This has one benefit, to begin with:
- value types always contains a value
- reference types can contain a null-reference, meaning that they don't refer to anything at all at the moment
Internally, reference types are implemented as pointers, and knowing that, and knowing how variable assignment works, there are other behavioral patterns:
- copying the contents of a value type variable into another variable, copies the entire contents into the new variable, making the two distinct. In other words, after the copy, changes to one won't affect the other
- copying the contents of a reference type variable into another variable, copies the reference, which means you now have two references to the same somewhere else storage of the actual data. In other words, after the copy, changing the data in one reference will appear to affect the other as well, but only because you're really just looking at the same data both places
When you declare variables or fields, here's how the two types differ:
- variable: value type lives on the stack, reference type lives on the stack as a pointer to somewhere in heap memory where the actual memory lives (though note Eric Lipperts article series: The Stack Is An Implementation Detail.)
- class/struct-field: value type lives completely inside the type, reference type lives inside the type as a pointer to somewhere in heap memory where the actual memory lives.
それぞれの短い要約:
クラスのみ:
- 継承をサポートできます
- 参照 (ポインター) 型である
- 参照はヌルにすることができます
- 新しいインスタンスごとにメモリ オーバーヘッドがある
構造体のみ:
- 継承をサポートできません
- 値型です
- 値渡し (整数など)
- null 参照を持つことはできません (Nullable が使用されている場合を除く)
- 新しいインスタンスごとにメモリ オーバーヘッドがない - 「ボックス化」されていない限り
クラスと構造体の両方:
- 通常、何らかの論理関係を持ついくつかの変数を含むために使用される複合データ型です
- メソッドとイベントを含めることができます
- インターフェイスをサポートできます
.NETでは、構造体宣言とクラス宣言は参照型と値型を区別します。
参照型を渡すと、実際に格納されるのは1つだけです。インスタンスにアクセスするすべてのコードは、同じインスタンスにアクセスしています。
値型を渡すと、それぞれがコピーになります。すべてのコードは独自のコピーで機能しています。
これは例で示すことができます:
struct MyStruct
{
string MyProperty { get; set; }
}
void ChangeMyStruct(MyStruct input)
{
input.MyProperty = "new value";
}
...
// Create value type
MyStruct testStruct = new MyStruct { MyProperty = "initial value" };
ChangeMyStruct(testStruct);
// Value of testStruct.MyProperty is still "initial value"
// - the method changed a new copy of the structure.
クラスの場合、これは異なります
class MyClass
{
string MyProperty { get; set; }
}
void ChangeMyClass(MyClass input)
{
input.MyProperty = "new value";
}
...
// Create reference type
MyClass testClass = new MyClass { MyProperty = "initial value" };
ChangeMyClass(testClass);
// Value of testClass.MyProperty is now "new value"
// - the method changed the instance passed.
クラスは何でもかまいません-参照はnullを指すことができます。
構造体は実際の値です。空にすることはできますが、nullになることはありません。このため、構造体には常にパラメーターのないデフォルトのコンストラクターがあります。「開始値」が必要です。
他の回答で説明されているすべての違いに加えて:
- 構造体は明示的なパラメーターなしのコンストラクターを持つことはできませんが、クラスは明示的にパラメーターなしのコンストラクターを持つことができます。
- 構造体はデストラクタを持つことができませんが、クラスはデストラクタを持つことができます
- 構造体は別の構造体またはクラスから継承できませんが、クラスは別のクラスから継承できます。(構造体とクラスの両方をインターフェイスから実装できます。)
すべての違いを説明するビデオをお探しの場合は、Part 29 - C# Tutorial - Difference between classes and structs in C# をご覧ください。
クラスのインスタンスは、管理対象ヒープに格納されます。インスタンスを「含む」すべての変数は、単にヒープ上のインスタンスへの参照です。オブジェクトをメソッドに渡すと、オブジェクト自体ではなく、参照のコピーが渡されます。
構造体(技術的には値型)は、プリミティブ型と同じように、使用される場所に格納されます。コンテンツは、カスタマイズされたコピーコンストラクターを呼び出さなくても、ランタイムによっていつでもコピーできます。値型をメソッドに渡すには、カスタマイズ可能なコードを呼び出さずに、値全体をコピーする必要があります。
区別は、C ++ / CLI名によってより明確になります。「refclass」は最初に説明されたクラスであり、「valueclass」は2番目に説明されたクラスです。C#で使用されるキーワード「class」と「struct」は、単に学習する必要があるものです。
構造とクラス
構造体は値型であるためスタックに格納されますが、クラスは参照型であり、ヒープに格納されます。
構造体は継承とポリモーフィズムをサポートしていませんが、クラスは両方をサポートしています。
デフォルトでは、すべての構造体メンバーはパブリックですが、クラスメンバーはデフォルトでプライベートです。
構造体は値型であるため、構造体オブジェクトにnullを割り当てることはできませんが、クラスの場合はそうではありません。
他の回答に加えて、注目に値する基本的な違いが 1 つあります。それは、パフォーマンスに大きな影響を与える可能性があるため、データが配列内に格納される方法です。
- 構造体の場合、配列には構造体のインスタンスが含まれます
- クラスの場合、配列にはメモリ内の別の場所にあるクラスのインスタンスへのポインタが含まれます
したがって、構造体の配列はメモリ内で次のようになります
[struct][struct][struct][struct][struct][struct][struct][struct]
クラスの配列は次のようになりますが、
[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]
クラスの配列では、関心のある値は配列内ではなく、メモリ内の別の場所に格納されます。
大多数のアプリケーションでは、この違いは実際には問題になりませんが、パフォーマンスの高いコードでは、メモリ内のデータの局所性に影響し、CPU キャッシュのパフォーマンスに大きな影響を与えます。構造体を使用できた/すべきだったときにクラスを使用すると、CPU でのキャッシュ ミスの数が大幅に増加します。
最新の CPU で最も遅いのは、数値を計算することではなく、メモリからデータをフェッチすることです。L1 キャッシュ ヒットは、RAM からデータを読み取るよりも何倍も高速です。
テストできるコードを次に示します。私のマシンでは、クラス配列を反復処理すると、構造体配列よりも ~3x 長くかかります。
private struct PerformanceStruct
{
public int i1;
public int i2;
}
private class PerformanceClass
{
public int i1;
public int i2;
}
private static void DoTest()
{
var structArray = new PerformanceStruct[100000000];
var classArray = new PerformanceClass[structArray.Length];
for (var i = 0; i < structArray.Length; i++)
{
structArray[i] = new PerformanceStruct();
classArray[i] = new PerformanceClass();
}
long total = 0;
var sw = new Stopwatch();
sw.Start();
for (var loops = 0; loops < 100; loops++)
for (var i = 0; i < structArray.Length; i++)
{
total += structArray[i].i1 + structArray[i].i2;
}
sw.Stop();
Console.WriteLine($"Struct Time: {sw.ElapsedMilliseconds}");
sw = new Stopwatch();
sw.Start();
for (var loops = 0; loops < 100; loops++)
for (var i = 0; i < classArray.Length; i++)
{
total += classArray[i].i1 + classArray[i].i2;
}
Console.WriteLine($"Class Time: {sw.ElapsedMilliseconds}");
}
さて、初心者にとって、構造体は参照ではなく値によって渡されます。構造体は比較的単純なデータ構造に適していますが、クラスはポリモーフィズムと継承を介してアーキテクチャの観点からはるかに柔軟性があります。
他の人はおそらく私よりも詳細を教えてくれるでしょうが、私が目指している構造が単純な場合は構造体を使用します。
クラスで宣言されたイベントの += および -= アクセスは、lock(this) を介して自動的にロックされ、スレッド セーフになります (静的イベントはクラスの型でロックされます)。構造体で宣言されたイベントの += および -= アクセスは自動的にロックされません。構造体の lock(this) は機能しません。参照型の式でしかロックできないからです。
構造体インスタンスを作成してもガベージ コレクションは発生しませんが (コンストラクターが直接または間接的に参照型インスタンスを作成しない限り)、参照型インスタンスを作成するとガベージ コレクションが発生する可能性があります。
構造体には常に組み込みのパブリック デフォルト コンストラクターがあります。
class DefaultConstructor { static void Eg() { Direct yes = new Direct(); // Always compiles OK InDirect maybe = new InDirect(); // Compiles if constructor exists and is accessible //... } }
つまり、構造体は常にインスタンス化可能ですが、クラスはすべてのコンストラクターがプライベートになる可能性があるため、インスタンス化できない可能性があります。
class NonInstantiable { private NonInstantiable() // OK { } } struct Direct { private Direct() // Compile-time error { } }
構造体にデストラクタを含めることはできません。デストラクタは object.Finalize の単なるオーバーライドであり、値型である構造体はガベージ コレクションの対象ではありません。
struct Direct { ~Direct() {} // Compile-time error } class InDirect { ~InDirect() {} // Compiles OK } And the CIL for ~Indirect() looks like this: .method family hidebysig virtual instance void Finalize() cil managed { // ... } // end of method Indirect::Finalize
構造体は暗黙的にシールされますが、クラスはそうではありません。
構造体は抽象化できませんが、クラスは抽象化できます。
構造体はコンストラクターで : base() を呼び出すことはできませんが、明示的な基底クラスを持たないクラスは呼び出すことができます。
構造体は別のクラスを拡張できませんが、クラスは拡張できます。
構造体は、クラスができる保護されたメンバー (フィールド、ネストされた型など) を宣言できません。
構造体は抽象関数メンバーを宣言できませんが、抽象クラスは宣言できます。
構造体は仮想関数メンバーを宣言できませんが、クラスは宣言できます。
構造体はシールされた関数メンバーを宣言できませんが、クラスは宣言できます。
構造体はオーバーライド関数のメンバーを宣言できませんが、クラスは宣言できます。
この規則の 1 つの例外は、構造体が System.Object、viz、Equals()、GetHashCode()、および ToString() の仮想メソッドをオーバーライドできることです。
前述のとおり、クラスは参照型ですが、構造体はすべての結果を持つ値型です。
原則として、Framework Design Guidelines では、次の場合にクラスではなく構造体を使用することを推奨しています。
- インスタンス サイズが 16 バイト未満である
- プリミティブ型 (int、double など) と同様に、単一の値を論理的に表します。
- 不変です
- 頻繁に箱詰めする必要はありません
構造体は実際の値です-空にすることはできますが、nullになることはありません
これは真実ですが、.NET 2の時点では、構造体はNullableバージョンをサポートしており、C#は使いやすくするために構文上の糖衣構文を提供していることにも注意してください。
int? value = null;
value = 1;
プリミティブ値型または構造体型のすべての変数またはフィールドは、そのすべてのフィールド (パブリックおよびプライベート) を含む、その型の一意のインスタンスを保持します。対照的に、参照型の変数またはフィールドは、null を保持するか、別の場所に格納されているオブジェクトを参照することができ、それらへの他の参照がいくつでも存在する可能性があります。構造体のフィールドは、その構造体型の変数またはフィールドと同じ場所に格納されます。これは、スタック上にあるか、別のヒープ オブジェクトの一部である可能性があります。
プリミティブ値型の変数またはフィールドを作成すると、デフォルト値で作成されます。構造型の変数またはフィールドを作成すると、新しいインスタンスが作成され、その中にすべてのフィールドがデフォルトの方法で作成されます。参照型の新しいインスタンスを作成するには、まずデフォルトの方法ですべてのフィールドを作成し、型に応じてオプションの追加コードを実行します。
プリミティブ型の変数またはフィールドを別の変数またはフィールドにコピーすると、値がコピーされます。ある変数または構造型のフィールドを別のインスタンスにコピーすると、前のインスタンスのすべてのフィールド (パブリックおよびプライベート) が後者のインスタンスにコピーされます。参照型の変数またはフィールドを別の変数またはフィールドにコピーすると、後者は前者と同じインスタンスを参照します (存在する場合)。
C++ などの一部の言語では、型のセマンティックな動作は格納方法とは無関係ですが、.NET には当てはまらないことに注意することが重要です。型が変更可能な値のセマンティクスを実装している場合、その型の 1 つの変数を別の型にコピーすると、最初の変数のプロパティが別のインスタンスにコピーされ、2 番目のインスタンスによって参照されます。2 番目のメンバーを使用して変更すると、2 番目のインスタンスが変更されます。 、しかし最初ではありません。型が変更可能な参照セマンティクスを実装している場合、ある変数を別の変数にコピーし、2 番目のメンバーを使用してオブジェクトを変更すると、最初の変数によって参照されるオブジェクトに影響します。不変のセマンティクスを持つ型は変更を許可しないため、コピーによって新しいインスタンスが作成されるか、最初のインスタンスへの別の参照が作成されるかは意味的に問題になりません。
.NET では、すべてのフィールドが同様に実行できる場合、値型が上記のセマンティクスのいずれかを実装することが可能です。ただし、参照型は可変参照セマンティクスまたは不変セマンティクスのみを実装できます。可変参照型のフィールドを持つ値型は、可変参照セマンティクスまたは奇妙なハイブリッド セマンティクスの実装に限定されます。