.NETの構造体とクラスの違いは何ですか?


回答:


1057

.NETには、参照タイプ値タイプという 2つのタイプのカテゴリがあります

構造体は値型であり、クラスは参照型です。

一般的な違いは、参照型はヒープ上に存在し、値型はインラインに存在することです。つまり、変数またはフィールドが定義されている場所であればどこにでも存在します。

値タイプを含む変数には、値タイプ値全体が含まれます。構造体の場合、これは、変数に構造体全体とそのすべてのフィールドが含まれていることを意味します。

参照型を含む変数には、ポインタ、または実際の値が存在するメモリ内の別の場所への参照が含まれています。

これには、まず次の1つの利点があります。

  • 値タイプには常にが含まれます
  • 参照型にnull 参照を含めることができます。つまり、現時点では何も参照していません。

内部的には、参照型はポインタとして実装されており、変数の割り当てがどのように機能するかを知っていることと、他の動作パターンがあります。

  • 値型変数の内容を別の変数にコピーし、内容全体を新しい変数にコピーして、2つを区別します。つまり、コピー後、一方を変更しても他方には影響しません
  • 参照型変数の内容を別の変数にコピーすると、参照がコピーされます。つまり、同じ場所への2つの参照が実際のデータの他の場所に保存されます。つまり、コピー後、1つの参照のデータを変更すると、もう1つの参照にも影響が及ぶように見えますが、これは、実際に両方の場所で同じデータを参照しているためです

変数またはフィールドを宣言する場合、2つのタイプの違いは次のとおりです。

  • 変数:値型はスタック上にあり、参照型は実際のメモリが存在するヒープメモリ内のどこかへのポインタとしてスタック上にあります(ただし、Eric Lippertsの記事シリーズ:スタックは実装の詳細です)。
  • class / struct-field:値型は完全に型の内部にあり、参照型は実際のメモリが存在するヒープメモリのどこかへのポインタとして型の内部にあります。

43
完全を期すために、Eric Lippertがスタックは実装の詳細であると述べたことに言及する必要があります。上記のスタックについて言及するときはいつでも、Ericの投稿を念頭に置いてください。
Lasse V. Karlsen、2011年

2
これはすべてC ++にも有効ですか?
Koray Tugay

9
別の重要な違いは使用法です。MSDNから:「構造体は通常、長方形の座標などの関連する変数の小さなグループをカプセル化するために使用されます。構造体には、コンストラクター、定数、フィールド、メソッド、プロパティ、インデクサー、演算子、イベント、およびネストされた型も含めることができます。メンバーは必須です。代わりに型をクラスにすることを検討してください。」
thewpfguy 2013

4
@KorayTugayいいえ、そうではありません。
ZoomIn 2013年

9
C ++構造体とクラスの@KorayTugayは、1つのことを除いて完全に同等です-デフォルトのアクセス制限(クラスにはデフォルトでプライベートがあり、構造体にはパブリックがあります)
berkus

207

それぞれの短い要約:

クラスのみ:

  • 継承をサポートできる
  • 参照(ポインタ)型です
  • 参照はnullにすることができます
  • 新しいインスタンスごとにメモリのオーバーヘッドがある

構造のみ:

  • 継承をサポートできない
  • 値型です
  • 値で渡される(整数など)
  • null参照は使用できません(Nullableが使用されている場合を除く)
  • 新しいインスタンスごとにメモリのオーバーヘッドが発生しない-「ボックス化」されない限り

クラスと構造体の両方:

  • いくつかの論理的な関係を持ついくつかの変数を含めるために通常使用される複合データ型ですか
  • メソッドとイベントを含めることができます
  • インターフェイスをサポートできます

16
この回答には、正しくない部分がいくつかあります。クラスは常にヒープに置かれるわけではなく、構造体は常にスタックに置かれるわけではありません。現在の例外には、クラスの構造体フィールド、無名メソッドとラムダ式でキャプチャされた変数、イテレータブロック、および前述のボックス化された値が含まれます。ただし、スタックとヒープの割り当ては実装の詳細であり、変更される可能性があります。エリックリップパートがこれについてここで議論します。私は反対票を投じましたが、更新すれば喜んで削除します。
Simon P Stevens

1
構造体は他の構造体/クラスからの継承をサポートしていませんが、構造体にインターフェイスを実装できます。
thewpfguy 2013

2
「新しいインスタンスごとにメモリオーバーヘッドを持たないでください」という構造を主張しているときに、何を意味するのかを明確にしたい場合があります。私の最初の解釈は、構造体はメモリをまったく使用しないと主張している-明らかにばかげている-とのことでした。次に、クラスとは異なり、構造体はそのメンバーフィールドの合計とまったく同じだけのメモリを必要とし、それ以上は必要ないと言っているのではないかと思いました。しかし、私はグーグルc# struct memory overhead検索、ハンスパッサントによるこの答えを見つけました。だから何をあなたはどういう意味ですか?
マークアメリー2017

4
@MarkAmery私は表現「ノーメモリのオーバーヘッド」にあなたのidと同じ初期の反応があったが、私はOPはのインスタンスがあるという事実に言及していることだと思うclassのインスタンスが、一方、(ガベージコレクタによって処理される)、メモリ管理されているstructではありません。
ハッチ、

1
"構造体は値で渡されます(整数など)"はfalseです。すべての変数は値で渡され、参照タイプも渡されます。変数を参照渡しする場合は、「ref」キーワードを使用する必要があります。jonskeet.uk/csharp/parameters.html#ref
Marco Staffoli 2017年

41

.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にすることはできません。このため、構造体には常にパラメータのないデフォルトのコンストラクタがあります。「開始値」が必要です。


@ T.Toduaええ、上記のより良い答えがあります。私が投票し、これを提供した後に答えとして選びました-これは、まだルールを理解していたときのSOの初期ベータ版からです。
キース

1
私はあなたが私を正しく理解しているかどうかわかりません、私はあなたの答えを(上記の答えとは対照的に)本当に賛成/受け入れました、なぜならあなたの良い例(理論的な説明だけではなく、理論的な説明だけでなく、理論的な説明しかなく)。
T.Todua

24

構造体とクラスの違い:

  • 構造体は値型ですがクラスは参照型です。
  • 構造体はスタック上に保存されているのに対し、クラスがヒープ上に格納されています
  • 値型は宣言されたメモリに値を保持しますが、参照型はオブジェクトメモリへの参照を保持します。
  • スコープが失われた直後破棄される値型は、スコープが失われた後に参照型のみが破棄されるのに対し、値型は破棄されます。オブジェクトは後でガベージコレクターによって破棄されます。
  • 構造体を別の構造体にコピーすると、その構造体の新しいコピーが作成され、1つの構造体が変更されても、他の構造体の値には影響しません。
  • クラスを別のクラスにコピーすると、参照変数のみがコピーされます。
  • どちらの参照変数も、ヒープ上の同じオブジェクトを指しています。1つの変数を変更すると、他の参照変数に影響します。
  • 構造体はデストラクタを持つことができません、クラスはデストラクタを持つことができます。
  • クラスは構造体を継承できませんが、クラスは継承をサポートしますが、構造体はパラメータなしのコンストラクタを明示的に持つことはできません。どちらもインターフェースからの継承をサポートしています。
  • 構造物は密閉型です。

21

Microsoftによるクラスと構造体選択から ...

経験則として、フレームワークの型の大部分はクラスでなければなりません。ただし、値型の特性によって構造体を使用する方が適切な場合もあります。

構造体を検討するクラスではなくを:

  • タイプのインスタンスが小さく、一般に短命であるか、一般に他のオブジェクトに埋め込まれている場合。

X は、型に次のすべての特性がない限り、構造体を避けます。

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

19

他の回答で説明されているすべての違いに加えて:

  1. 構造体は、明示的なパラメータなしのコンストラクタを持つことはできませんクラス缶のに対し、
  2. 構造 はデストラクタを持つことができませんが、クラスは
  3. 構造体が継承することはできませんクラスが別のクラスから継承することができ、一方、別の構造体やクラスから。(構造体とクラスの両方がインターフェイスから実装できます。)

すべての違いを説明するビデオが必要な場合は、パート29-C#チュートリアル-C#のクラスと構造体の違いを確認してください。


4
.net言語では通常、構造体がパラメーターなしのコンストラクターを定義できない(言語コンパイラーによって作成されるかどうかの決定)ことができないという事実よりもはるかに重要なのは、構造体が存在して公開される可能性があるという事実です。 (パラメーターのないコンストラクターが定義されている場合でも)実行されているコンストラクターのない外部の世界へ。.net言語が構造体のパラメーターなしのコンストラクターを一般的に禁止する理由は、そのようなコンストラクターが実行される場合と実行されない場合があることに起因する混乱を回避するためです。
スーパーキャット2012年

15

クラスのインスタンスはマネージヒープに格納されます。インスタンスを「含む」すべての変数は、単にヒープ上のインスタンスへの参照です。オブジェクトをメソッドに渡すと、オブジェクト自体ではなく、参照のコピーが渡されます。

構造体(技術的には値型)は、プリミティブ型のように、使用される場所に保存されます。コンテンツは、カスタマイズされたコピーコンストラクターを呼び出すことなく、いつでもランタイムによってコピーできます。値型をメソッドに渡すには、カスタマイズ可能なコードを呼び出さずに、値全体をコピーする必要があります。

この区別は、C ++ / CLIの名前によってより明確になります。「refクラス」は最初に説明したクラスであり、「値クラス」は2番目に説明したクラスです。C#で使用されるキーワード「クラス」と「構造体」は、単に学習する必要があるものです。


11
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
|                        |                                                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                                |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+

1
これは実際には非常にすばらしいものです。要約されており、有益です。少なくとも1度は校正を行うことを忘れないでください。いくつかの行で構造体とクラスの説明を入れ替えました。また、いくつかのタイプミスもあります。
Robert Synoradzki

1
@ensisNoctisこれらの間違いをお詫びし、編集してくれてありがとう。私は😅私の答えを再読み込みする必要があり
0xaryan

8

構造とクラス

構造体は値型であるため、スタックに格納されますが、クラスは参照型であり、ヒープに格納されます。

構造は継承とポリモーフィズムをサポートしていませんが、クラスは両方をサポートしています。

デフォルトでは、すべての構造体メンバーはパブリックですが、クラスメンバーはデフォルトでプライベートです。

構造体は値型なので、構造体オブジェクトにnullを割り当てることはできませんが、クラスの場合はそうではありません。


5
「すべての構造体メンバーは公開されています」に関して:私が間違っていなければ、それは誤りです。「ネストされたクラスと構造体を含む、クラスメンバーと構造体メンバーのアクセスレベルは、デフォルトではプライベートです。」msdn.microsoft.com/en-us/library/ms173121.aspx
Nate Cook

8

他の回答に加えて、注目に値する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}");
    }

-1; 「構造体は値型なので、値を格納し、クラスは参照型であるため、クラスを参照します。」ここで他の答えからそれをまだ理解していない誰にとっても不明確で意味をなさそうにありません、そして「クラスでは、包含クラスはメモリの異なる領域の新しいクラスへのポインタを含むだけです。」クラスをクラスインスタンスと混同します。
Mark Amery 2017

@MarkAmery少し明確にしようとしました。私が実際に試みた点は、配列が値と参照型を処理する方法の違いと、これがパフォーマンスに及ぼす影響です。他の多くの答えで行われているので、私は値と参照タイプが何であるかを再説明しようとしませんでした。
ウィルカルダーウッド2018

7

完全にするために、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人です。


6

まず、構造体は参照ではなく値で渡されます。構造は、比較的単純なデータ構造に適していますが、クラスは、構造的観点から、ポリモーフィズムと継承を介して、より多くの柔軟性を備えています。

他のものはおそらく私よりも詳細な情報を提供する可能性がありますが、目的の構造が単純な場合は構造体を使用します。


4

アクセス指定子の基本的な違い、および上記のいくつかに加えて、上記のいくつかを含む主要な違いのいくつかを出力付きのコードサンプルに追加します。これにより、参照と値がより明確になります。

構造:

  • 値型であり、ヒープ割り当てを必要としません。
  • メモリ割り当てが異なり、スタックに格納されています
  • 小さなデータ構造に役立ちます
  • パフォーマンスに影響します。メソッドに値を渡すと、データ構造全体が渡され、すべてがスタックに渡されます。
  • コンストラクターは単に構造体の値自体(通常はスタック上の一時的な場所)を返し、この値は必要に応じてコピーされます
  • 変数にはそれぞれ独自のデータのコピーがあり、一方の操作が他方に影響を与えることはできません。
  • ユーザー指定の継承をサポートせず、暗黙的に型オブジェクトから継承する

クラス:

  • 参照タイプの値
  • ヒープに格納
  • 動的に割り当てられたオブジェクトへの参照を保存する
  • コンストラクタはnew演算子で呼び出されますが、ヒープにメモリを割り当てません
  • 複数の変数が同じオブジェクトへの参照を持つ場合があります
  • 1つの変数に対する操作が、他の変数によって参照されるオブジェクトに影響を与える可能性があります

コードサンプル

    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

ここでは、値による呼び出しと参照による呼び出しの違いを明確に見ることができます。


4
  1. クラスで宣言されたイベントの+ =および-=アクセスは、lock(this)を介して自動的にロックされ、スレッドセーフになります(静的イベントはクラスのtypeでロックされます)。構造体で宣言されたイベントには、+ =および-=アクセスが自動的にロックされません。参照型の式でのみロックできるため、構造体のロック(this)は機能しません。

  2. 構造体インスタンスを作成してもガベージコレクションは発生しません(コンストラクターが直接または間接的に参照型インスタンスを作成しない場合)。参照型インスタンスを作成するとガベージコレクションが発生する可能性があります。

  3. 構造体には、常に組み込みのデフォルトのコンストラクタがあります。

    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
        {
        }
    }
  4. 構造体にデストラクタを含めることはできません。デストラクタはオブジェクトのオーバーライドにすぎません。偽のファイナライズは、値の型である構造体はガベージコレクションの対象ではありません。

    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
  5. 構造体は暗黙的に封印されていますが、クラスは封印されていません。
    構造体は抽象であってはなりません、クラスはできます。
    構造体はコンストラクタでbase()を呼び出すことはできませんが、明示的な基本クラスのないクラスは呼び出すことができます。
    構造体は別のクラスを拡張できません。クラスは拡張できます。
    構造体は、クラスができる保護されたメンバー(たとえば、フィールド、ネストされた型)を宣言できません。
    構造体は抽象関数のメンバーを宣言できません。抽象クラスは宣言できます。
    構造体は仮想関数のメンバーを宣言できませんが、クラスは宣言できます。
    構造体は、シールされた関数メンバーを宣言できません。クラスは宣言できます。
    構造体はオーバーライド関数のメンバーを宣言できません。クラスは宣言できます。
    このルールの1つの例外は、構造体がSystem.Object、viz、Equals()、GetHashCode()、およびToString()の仮想メソッドをオーバーライドできることです。


どのような状況で、構造体を持つイベントを使用しますか?非常に注意深く作成されたプログラムが、構造を持つイベントを機能する方法で使用できると想像できますが、構造が値によってコピーまたは渡されなかった場合に限り、その場合は、クラスでもある可能性があります。
スーパーキャット

@supercatええ、構造体の非静的イベントは非常に奇妙で、変更可能な構造体にのみ役立ちます。イベント自体(「フィールドのような」イベントの場合)は、構造体を「可変"カテゴリと、構造体に参照型のフィールドを導入します。構造体の非静的イベントは悪である必要があります。
Jeppe Stig Nielsen 2014

@JeppeStigNielsen:構造体がイベントを持つことが理にかなっている唯一のパターンは、構造体の目的が、それがプロキシとして動作したクラスオブジェクトへの不変の参照を保持することである場合です。ただし、このようなシナリオでは自動イベントはまったく役に立ちません。代わりに、サブスクライブおよびサブスクライブ解除イベントを構造の背後にあるクラスにリレーする必要があります。私は、.NETがあればいいのに(または、それが可能に定義することになるだろう)「タイプの最初はヌル隠されたフィールドで、キャッシュボックス」構造タイプのObject構造体の箱入りコピーへの参照を保持するだろう。
supercat

1
@JeppeStigNielsen:構造体は、多くのプロキシ使用シナリオでクラスよりも優れています。構造体を使用する際の最大の問題は、ボクシングが必要になる場合に、多くの場合、内部ループに延期されることです。構造体が繰り返しボックス化されるのを回避する方法があった場合、より多くの使用シナリオでクラスよりも優れています。
スーパーキャット2014年

4

前述のとおり:クラスは参照型であり、構造体はすべての結果を持つ値型です。

原則として、フレームワーク設計ガイドラインでは、次の場合にクラスの代わりに構造体を使用することを推奨しています。

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

3

「クラス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;
            }
        }
    }
}

2

構造体は実際の値です-空にすることもできますが、nullにすることはできません

これは真実ですが、.NET 2の構造体ではNullableバージョンがサポートされており、C#は使いやすくするためにいくつかの構文糖を提供していることにも注意してください。

int? value = null;
value  = 1;

1
これは 'Nullable <int> value = null;'を読み取る唯一の構文糖であることに注意してください。
エリックファン

@ErikvanBrakelそれは単なる構文上の砂糖ではありません。さまざまなボクシングルールは(object)(default(int?)) == null、他の値タイプでは実行できないことを意味します。これは、砂糖だけではありません。唯一の砂糖はのint?ためNullable<int>です。
Jon Hanna

-1; これは、構造体とクラスの違いが何であるかという問題には対応していません。そのため、個別の回答ではなく、返信している回答に対するコメントである必要があります。(おそらく、サイトの基準は2008年8月にさかのぼって変更されましたが!)
Mark Amery 2017

1

プリミティブ値型または構造体型のすべての変数またはフィールドは、そのフィールド(パブリックおよびプライベート)を含む、その型の一意のインスタンスを保持します。対照的に、参照型の変数またはフィールドはnullを保持するか、他の場所に格納されているオブジェクトを参照する場合があり、他の参照もいくつでも存在する可能性があります。構造体のフィールドは、その構造体型の変数またはフィールドと同じ場所に格納されます。これは、スタック上にあるか別のヒープオブジェクトの一部である可能性があります。

プリミティブ値型の変数またはフィールドを作成すると、デフォルト値で作成されます。変数または構造体タイプのフィールドを作成すると、新しいインスタンスが作成され、その中にすべてのフィールドがデフォルトの方法で作成されます。参照型の新しいインスタンスを作成するには、まずデフォルトの方法ですべてのフィールドを作成し、次に型に応じてオプションの追加コードを実行します。

プリミティブ型の1つの変数またはフィールドを別の型にコピーすると、値がコピーされます。1つの変数または構造体型のフィールドを別のフィールドにコピーすると、前のインスタンスのすべてのフィールド(パブリックおよびプライベート)が後者のインスタンスにコピーされます。1つの変数または参照型のフィールドを別のフィールドにコピーすると、後者は前者と同じインスタンスを参照します(存在する場合)。

C ++のような一部の言語では、型のセマンティック動作は格納方法に依存しないことに注意することが重要ですが、それは.NETには当てはまりません。型が可変値のセマンティクスを実装している場合、その型のある変数を別の変数にコピーすると、最初のプロパティが別のインスタンスにコピーされ、2番目のインスタンスによって参照されます。2番目のメンバーを使用してそれを変更すると、2番目のインスタンスが変更されます、しかし最初ではない。型が可変参照セマンティクスを実装している場合、1つの変数を別の変数にコピーし、2番目のメンバーを使用してオブジェクトを変更すると、最初の変数が参照するオブジェクトに影響します。不変のセマンティクスを持つ型は変異を許可しないため、コピーによって新しいインスタンスが作成されるか、最初のインスタンスへの別の参照が作成されるかは、意味的には問題になりません。

.NETでは、すべてのフィールドが同様に実行できる場合、値タイプが上記のセマンティクスのいずれかを実装することが可能です。ただし、参照型は、変更可能な参照セマンティクスまたは不変のセマンティクスのみを実装できます。可変参照型のフィールドを持つ値型は、可変参照セマンティクスまたは奇妙なハイブリッドセマンティクスの実装に制限されています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.