回答:
.NETには、参照タイプと値タイプという 2つのタイプのカテゴリがあります。
構造体は値型であり、クラスは参照型です。
一般的な違いは、参照型はヒープ上に存在し、値型はインラインに存在することです。つまり、変数またはフィールドが定義されている場所であればどこにでも存在します。
値タイプを含む変数には、値タイプ値全体が含まれます。構造体の場合、これは、変数に構造体全体とそのすべてのフィールドが含まれていることを意味します。
参照型を含む変数には、ポインタ、または実際の値が存在するメモリ内の別の場所への参照が含まれています。
これには、まず次の1つの利点があります。
内部的には、参照型はポインタとして実装されており、変数の割り当てがどのように機能するかを知っていることと、他の動作パターンがあります。
変数またはフィールドを宣言する場合、2つのタイプの違いは次のとおりです。
それぞれの短い要約:
クラスのみ:
構造のみ:
クラスと構造体の両方:
c# struct memory overhead
検索し、ハンスパッサントによるこの答えを見つけました。だから何をかあなたはどういう意味ですか?
class
のインスタンスが、一方、(ガベージコレクタによって処理される)、メモリ管理されているstruct
ではありません。
.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にすることはできません。このため、構造体には常にパラメータのないデフォルトのコンストラクタがあります。「開始値」が必要です。
構造体とクラスの違い:
経験則として、フレームワークの型の大部分はクラスでなければなりません。ただし、値型の特性によって構造体を使用する方が適切な場合もあります。
✓ 構造体を検討するクラスではなくを:
- タイプのインスタンスが小さく、一般に短命であるか、一般に他のオブジェクトに埋め込まれている場合。
X は、型に次のすべての特性がない限り、構造体を避けます。
- プリミティブ型(int、doubleなど)と同様に、論理的に単一の値を表します。
- インスタンスサイズは16バイト未満です。
- それは不変です。(変えられない)
- 頻繁にボックス化する必要はありません。
他の回答で説明されているすべての違いに加えて:
すべての違いを説明するビデオが必要な場合は、パート29-C#チュートリアル-C#のクラスと構造体の違いを確認してください。
クラスのインスタンスはマネージヒープに格納されます。インスタンスを「含む」すべての変数は、単にヒープ上のインスタンスへの参照です。オブジェクトをメソッドに渡すと、オブジェクト自体ではなく、参照のコピーが渡されます。
構造体(技術的には値型)は、プリミティブ型のように、使用される場所に保存されます。コンテンツは、カスタマイズされたコピーコンストラクターを呼び出すことなく、いつでもランタイムによってコピーできます。値型をメソッドに渡すには、カスタマイズ可能なコードを呼び出さずに、値全体をコピーする必要があります。
この区別は、C ++ / CLIの名前によってより明確になります。「refクラス」は最初に説明したクラスであり、「値クラス」は2番目に説明したクラスです。C#で使用されるキーワード「クラス」と「構造体」は、単に学習する必要があるものです。
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
| | Struct | Class |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
| Type | Value-type | Reference-type |
| Where | On stack / Inline in containing type | On Heap |
| Deallocation | Stack unwinds / containing type gets deallocated | Garbage Collected |
| Arrays | Inline, elements are the actual instances of the value type | Out of line, elements are just references to instances of the reference type residing on the heap |
| Aldel Cost | Cheap allocation-deallocation | Expensive allocation-deallocation |
| Memory usage | Boxed when cast to a reference type or one of the interfaces they implement, | No boxing-unboxing |
| | Unboxed when cast back to value type | |
| | (Negative impact because boxes are objects that are allocated on the heap and are garbage-collected) | |
| Assignments | Copy entire data | Copy the reference |
| Change to an instance | Does not affect any of its copies | Affect all references pointing to the instance |
| Mutability | Should be immutable | Mutable |
| Population | In some situations | Majority of types in a framework should be classes |
| Lifetime | Short-lived | Long-lived |
| Destructor | Cannot have | Can have |
| Inheritance | Only from an interface | Full support |
| Polymorphism | No | Yes |
| Sealed | Yes | When have sealed keyword |
| Constructor | Can not have explicit parameterless constructors | Any constructor |
| Null-assignments | When marked with nullable question mark | Yes (+ When marked with nullable question mark in C# 8+) |
| Abstract | No | When have abstract keyword |
| Member Access Modifiers| public, private, internal | public, protected, internal, protected internal, private protected |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
構造とクラス
構造体は値型であるため、スタックに格納されますが、クラスは参照型であり、ヒープに格納されます。
構造は継承とポリモーフィズムをサポートしていませんが、クラスは両方をサポートしています。
デフォルトでは、すべての構造体メンバーはパブリックですが、クラスメンバーはデフォルトでプライベートです。
構造体は値型なので、構造体オブジェクトにnullを割り当てることはできませんが、クラスの場合はそうではありません。
他の回答に加えて、注目に値する1つの基本的な違いがあります。これは、パフォーマンスに大きな影響を与える可能性があるため、配列内にデータを格納する方法です。
したがって、構造体の配列はメモリ内では次のようになります
[struct][struct][struct][struct][struct][struct][struct][struct]
一方、クラスの配列は次のようになります
[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]
クラスの配列では、必要な値は配列内ではなく、メモリ内の別の場所に格納されます。
ほとんどのアプリケーションでは、この違いはそれほど重要ではありませんが、高パフォーマンスのコードでは、メモリ内のデータの局所性に影響し、CPUキャッシュのパフォーマンスに大きな影響を与えます。構造体を使用する可能性がある場合にクラスを使用すると、CPUでのキャッシュミスの数が大幅に増加します。
最近のCPUが最も遅いのは、数値を処理することではなく、メモリからデータをフェッチすることであり、L1キャッシュヒットはRAMからデータを読み取るよりも何倍も高速です。
テストできるコードをいくつか示します。私のマシンでは、クラス配列の繰り返し処理は、構造体配列よりも最大3倍長くかかります。
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}");
}
完全にするために、Equals
すべてのクラスと構造によって継承されるメソッドを使用すると、別の違いがあります。
クラスと構造があるとしましょう:
class A{
public int a, b;
}
struct B{
public int a, b;
}
Mainメソッドには4つのオブジェクトがあります。
static void Main{
A c1 = new A(), c2 = new A();
c1.a = c1.b = c2.a = c2.b = 1;
B s1 = new B(), s2 = new B();
s1.a = s1.b = s2.a = s2.b = 1;
}
次に:
s1.Equals(s2) // true
s1.Equals(c1) // false
c1.Equals(c2) // false
c1 == c2 // false
したがって、構造体は、ポイントのような数値のようなオブジェクト(x座標とy座標を保存)に適しています。そしてクラスは他の人に適しています。同じ名前、身長、体重の2人がいても2人です。
アクセス指定子の基本的な違い、および上記のいくつかに加えて、上記のいくつかを含む主要な違いのいくつかを出力付きのコードサンプルに追加します。これにより、参照と値がより明確になります。
構造:
クラス:
コードサンプル
static void Main(string[] args)
{
//Struct
myStruct objStruct = new myStruct();
objStruct.x = 10;
Console.WriteLine("Initial value of Struct Object is: " + objStruct.x);
Console.WriteLine();
methodStruct(objStruct);
Console.WriteLine();
Console.WriteLine("After Method call value of Struct Object is: " + objStruct.x);
Console.WriteLine();
//Class
myClass objClass = new myClass(10);
Console.WriteLine("Initial value of Class Object is: " + objClass.x);
Console.WriteLine();
methodClass(objClass);
Console.WriteLine();
Console.WriteLine("After Method call value of Class Object is: " + objClass.x);
Console.Read();
}
static void methodStruct(myStruct newStruct)
{
newStruct.x = 20;
Console.WriteLine("Inside Struct Method");
Console.WriteLine("Inside Method value of Struct Object is: " + newStruct.x);
}
static void methodClass(myClass newClass)
{
newClass.x = 20;
Console.WriteLine("Inside Class Method");
Console.WriteLine("Inside Method value of Class Object is: " + newClass.x);
}
public struct myStruct
{
public int x;
public myStruct(int xCons)
{
this.x = xCons;
}
}
public class myClass
{
public int x;
public myClass(int xCons)
{
this.x = xCons;
}
}
出力
Structオブジェクトの初期値は10です。
構造体メソッドの内部構造体オブジェクトのメソッドの内部値は次のとおりです:20
Structオブジェクトのメソッド呼び出し後の値は10です。
クラスオブジェクトの初期値は10です。
クラスメソッドの内側クラスオブジェクトのメソッドの内側の値は:20
クラスオブジェクトのメソッド呼び出し後の値は次のとおりです:20
ここでは、値による呼び出しと参照による呼び出しの違いを明確に見ることができます。
クラスで宣言されたイベントの+ =および-=アクセスは、lock(this)を介して自動的にロックされ、スレッドセーフになります(静的イベントはクラスのtypeでロックされます)。構造体で宣言されたイベントには、+ =および-=アクセスが自動的にロックされません。参照型の式でのみロックできるため、構造体のロック(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
{
}
}
構造体にデストラクタを含めることはできません。デストラクタはオブジェクトのオーバーライドにすぎません。偽のファイナライズは、値の型である構造体はガベージコレクションの対象ではありません。
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()の仮想メソッドをオーバーライドできることです。
Object
構造体の箱入りコピーへの参照を保持するだろう。
「クラスvs構造体」パズルの興味深いケースが1つあります。メソッドから複数の結果を返す必要がある場合、どちらを使用するかを選択します。ValueTupleストーリーを知っていれば、Tuple(クラス)よりも効果的であるため、ValueTuple(構造体)が追加されたことがわかります。しかし、それは数字で何を意味しますか?2つのテスト:1つは2つのフィールドを持つstruct / classで、もう1つは8つのフィールドを持つstruct / classです(次元が4よりも大きい-クラスはプロセッサティックの観点でより効果的である必要がありますが、もちろんGC負荷も考慮する必要があります) )。
PS特定のケース「コレクションのある構造またはクラス」の別のベンチマークがあります:https ://stackoverflow.com/a/45276657/506147
BenchmarkDotNet=v0.10.10, OS=Windows 10 Redstone 2 [1703, Creators Update] (10.0.15063.726)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233540 Hz, Resolution=309.2586 ns, Timer=TSC
.NET Core SDK=2.0.3
[Host] : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
Clr : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2115.0
Core : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
Method | Job | Runtime | Mean | Error | StdDev | Min | Max | Median | Rank | Gen 0 | Allocated |
------------------ |----- |-------- |---------:|----------:|----------:|---------:|---------:|---------:|-----:|-------:|----------:|
TestStructReturn | Clr | Clr | 17.57 ns | 0.1960 ns | 0.1834 ns | 17.25 ns | 17.89 ns | 17.55 ns | 4 | 0.0127 | 40 B |
TestClassReturn | Clr | Clr | 21.93 ns | 0.4554 ns | 0.5244 ns | 21.17 ns | 23.26 ns | 21.86 ns | 5 | 0.0229 | 72 B |
TestStructReturn8 | Clr | Clr | 38.99 ns | 0.8302 ns | 1.4097 ns | 37.36 ns | 42.35 ns | 38.50 ns | 8 | 0.0127 | 40 B |
TestClassReturn8 | Clr | Clr | 23.69 ns | 0.5373 ns | 0.6987 ns | 22.70 ns | 25.24 ns | 23.37 ns | 6 | 0.0305 | 96 B |
TestStructReturn | Core | Core | 12.28 ns | 0.1882 ns | 0.1760 ns | 11.92 ns | 12.57 ns | 12.30 ns | 1 | 0.0127 | 40 B |
TestClassReturn | Core | Core | 15.33 ns | 0.4343 ns | 0.4063 ns | 14.83 ns | 16.44 ns | 15.31 ns | 2 | 0.0229 | 72 B |
TestStructReturn8 | Core | Core | 34.11 ns | 0.7089 ns | 1.4954 ns | 31.52 ns | 36.81 ns | 34.03 ns | 7 | 0.0127 | 40 B |
TestClassReturn8 | Core | Core | 17.04 ns | 0.2299 ns | 0.2150 ns | 16.68 ns | 17.41 ns | 16.98 ns | 3 | 0.0305 | 96 B |
コードテスト:
using System;
using System.Text;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Columns;
using BenchmarkDotNet.Attributes.Exporters;
using BenchmarkDotNet.Attributes.Jobs;
using DashboardCode.Routines.Json;
namespace Benchmark
{
//[Config(typeof(MyManualConfig))]
[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
[ClrJob, CoreJob]
[HtmlExporter, MarkdownExporter]
[MemoryDiagnoser]
public class BenchmarkStructOrClass
{
static TestStruct testStruct = new TestStruct();
static TestClass testClass = new TestClass();
static TestStruct8 testStruct8 = new TestStruct8();
static TestClass8 testClass8 = new TestClass8();
[Benchmark]
public void TestStructReturn()
{
testStruct.TestMethod();
}
[Benchmark]
public void TestClassReturn()
{
testClass.TestMethod();
}
[Benchmark]
public void TestStructReturn8()
{
testStruct8.TestMethod();
}
[Benchmark]
public void TestClassReturn8()
{
testClass8.TestMethod();
}
public class TestStruct
{
public int Number = 5;
public struct StructType<T>
{
public T Instance;
public List<string> List;
}
public int TestMethod()
{
var s = Method1(1);
return s.Instance;
}
private StructType<int> Method1(int i)
{
return Method2(++i);
}
private StructType<int> Method2(int i)
{
return Method3(++i);
}
private StructType<int> Method3(int i)
{
return Method4(++i);
}
private StructType<int> Method4(int i)
{
var x = new StructType<int>();
x.List = new List<string>();
x.Instance = ++i;
return x;
}
}
public class TestClass
{
public int Number = 5;
public class ClassType<T>
{
public T Instance;
public List<string> List;
}
public int TestMethod()
{
var s = Method1(1);
return s.Instance;
}
private ClassType<int> Method1(int i)
{
return Method2(++i);
}
private ClassType<int> Method2(int i)
{
return Method3(++i);
}
private ClassType<int> Method3(int i)
{
return Method4(++i);
}
private ClassType<int> Method4(int i)
{
var x = new ClassType<int>();
x.List = new List<string>();
x.Instance = ++i;
return x;
}
}
public class TestStruct8
{
public int Number = 5;
public struct StructType<T>
{
public T Instance1;
public T Instance2;
public T Instance3;
public T Instance4;
public T Instance5;
public T Instance6;
public T Instance7;
public List<string> List;
}
public int TestMethod()
{
var s = Method1(1);
return s.Instance1;
}
private StructType<int> Method1(int i)
{
return Method2(++i);
}
private StructType<int> Method2(int i)
{
return Method3(++i);
}
private StructType<int> Method3(int i)
{
return Method4(++i);
}
private StructType<int> Method4(int i)
{
var x = new StructType<int>();
x.List = new List<string>();
x.Instance1 = ++i;
return x;
}
}
public class TestClass8
{
public int Number = 5;
public class ClassType<T>
{
public T Instance1;
public T Instance2;
public T Instance3;
public T Instance4;
public T Instance5;
public T Instance6;
public T Instance7;
public List<string> List;
}
public int TestMethod()
{
var s = Method1(1);
return s.Instance1;
}
private ClassType<int> Method1(int i)
{
return Method2(++i);
}
private ClassType<int> Method2(int i)
{
return Method3(++i);
}
private ClassType<int> Method3(int i)
{
return Method4(++i);
}
private ClassType<int> Method4(int i)
{
var x = new ClassType<int>();
x.List = new List<string>();
x.Instance1 = ++i;
return x;
}
}
}
}
構造体は実際の値です-空にすることもできますが、nullにすることはできません
これは真実ですが、.NET 2の構造体ではNullableバージョンがサポートされており、C#は使いやすくするためにいくつかの構文糖を提供していることにも注意してください。
int? value = null;
value = 1;
(object)(default(int?)) == null
、他の値タイプでは実行できないことを意味します。これは、砂糖だけではありません。唯一の砂糖はのint?
ためNullable<int>
です。
プリミティブ値型または構造体型のすべての変数またはフィールドは、そのフィールド(パブリックおよびプライベート)を含む、その型の一意のインスタンスを保持します。対照的に、参照型の変数またはフィールドはnullを保持するか、他の場所に格納されているオブジェクトを参照する場合があり、他の参照もいくつでも存在する可能性があります。構造体のフィールドは、その構造体型の変数またはフィールドと同じ場所に格納されます。これは、スタック上にあるか、別のヒープオブジェクトの一部である可能性があります。
プリミティブ値型の変数またはフィールドを作成すると、デフォルト値で作成されます。変数または構造体タイプのフィールドを作成すると、新しいインスタンスが作成され、その中にすべてのフィールドがデフォルトの方法で作成されます。参照型の新しいインスタンスを作成するには、まずデフォルトの方法ですべてのフィールドを作成し、次に型に応じてオプションの追加コードを実行します。
プリミティブ型の1つの変数またはフィールドを別の型にコピーすると、値がコピーされます。1つの変数または構造体型のフィールドを別のフィールドにコピーすると、前のインスタンスのすべてのフィールド(パブリックおよびプライベート)が後者のインスタンスにコピーされます。1つの変数または参照型のフィールドを別のフィールドにコピーすると、後者は前者と同じインスタンスを参照します(存在する場合)。
C ++のような一部の言語では、型のセマンティック動作は格納方法に依存しないことに注意することが重要ですが、それは.NETには当てはまりません。型が可変値のセマンティクスを実装している場合、その型のある変数を別の変数にコピーすると、最初のプロパティが別のインスタンスにコピーされ、2番目のインスタンスによって参照されます。2番目のメンバーを使用してそれを変更すると、2番目のインスタンスが変更されます、しかし最初ではない。型が可変参照セマンティクスを実装している場合、1つの変数を別の変数にコピーし、2番目のメンバーを使用してオブジェクトを変更すると、最初の変数が参照するオブジェクトに影響します。不変のセマンティクスを持つ型は変異を許可しないため、コピーによって新しいインスタンスが作成されるか、最初のインスタンスへの別の参照が作成されるかは、意味的には問題になりません。
.NETでは、すべてのフィールドが同様に実行できる場合、値タイプが上記のセマンティクスのいずれかを実装することが可能です。ただし、参照型は、変更可能な参照セマンティクスまたは不変のセマンティクスのみを実装できます。可変参照型のフィールドを持つ値型は、可変参照セマンティクスまたは奇妙なハイブリッドセマンティクスの実装に制限されています。