クラスではなく構造体をいつ使用すればよいですか?


302

MSDNでは、軽量オブジェクトが必要な場合は構造体を使用する必要があると述べています。構造体がクラスよりも望ましい他のシナリオはありますか?

一部の人々はそれを忘れているかもしれません:

  1. 構造体はメソッドを持つことができます。
  2. 構造体は継承できません。

構造体とクラスの技術的な違いを理解していますが、構造体をいつ使用するかがわかりません。


ただ注意しておきます。このコンテキストでほとんどの人が忘れがちなのは、C#では構造体にもメソッドを含めることができるということです。
petr k。

回答:


295

MSDNには答えがあります: クラスと構造の選択

基本的に、そのページには4項目のチェックリストがあり、タイプがすべての基準を満たさない限り、クラスを使用するように指示されています。

タイプに次のすべての特性がない限り、構造を定義しないでください。

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

1
多分私は明白な何かを見逃しているかもしれませんが、私は「不変」の部分の背後にある理由をまったく理解していません。なぜこれが必要なのですか?誰かがそれを説明できますか?
Tamas Czinege、2009年

3
構造体が不変である場合、構造体が参照セマンティクスではなく値セマンティクスを持っていることは問題にならないため、彼らはおそらくこれを推奨しています。区別は、コピーを作成した後にオブジェクト/構造体を変更する場合にのみ重要です。
スティーブンC.スティール、

@DrJokepu:状況によっては、システムが構造体の一時的なコピーを作成し、それを変更するコードへの参照によってそのコピーを渡すことができるようにします。一時的なコピーは破棄されるため、変更は失われます。この問題は、構造体にそれを変更するメソッドがある場合は特に深刻です。私は、可変性が何かをクラスにする理由であるという考えに強く反対します。c#とvb.netにいくつかの欠陥があるにもかかわらず、可変構造体は他の方法では達成できない有用なセマンティクスを提供するためです。クラスよりも不変の構造体を好む意味論的な理由はありません。
スーパーキャット2011年

4
@Chuu:JITコンパイラの設計において、Microsoftは16バイト以下の構造体をコピーするためにコードを最適化することを決定しました。つまり、17バイトの構造体をコピーすると、16バイトの構造体をコピーするよりも大幅に遅くなる可能性があります。Microsoftがそのような最適化をより大きな構造体に拡張することを期待する特別な理由はありませんが、17バイトの構造体は16バイトの構造体よりもコピーに時間がかかる場合がありますが、大きな構造体の方がより効率的である場合が多いことに注意することが重要ですラージクラスオブジェクト。構造体の相対的な利点は、構造体のサイズとともに大きくなります。
スーパーキャット2013年

3
@Chuu:大きな構造にクラスと同じ使用パターンを適用すると、コードが非効率になる傾向がありますが、適切な解決策では、構造体をクラスに置き換えず、構造体をより効率的に使用することがよくあります。特に、値で構造体を渡したり、返したりすることは避けてください。refそうすることが合理的であるときはいつでも、それらをパラメーターとして渡します。4,000のフィールドを持つ構造体をrefパラメーターとしてメソッドに渡すと、変更されたバージョンを返すメソッドに値によって4つのフィールドを持つ構造体を渡すよりも安上がりになります。
スーパーキャット2013年

53

これまでの回答でこれを読んでいないことに驚いています。これは最も重要な側面だと思います。

IDのない型が必要な場合は、構造体を使用します。たとえば、3Dポイント:

public struct ThreeDimensionalPoint
{
    public readonly int X, Y, Z;
    public ThreeDimensionalPoint(int x, int y, int z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }

    public override string ToString()
    {
        return "(X=" + this.X + ", Y=" + this.Y + ", Z=" + this.Z + ")";
    }

    public override int GetHashCode()
    {
        return (this.X + 2) ^ (this.Y + 2) ^ (this.Z + 2);
    }

    public override bool Equals(object obj)
    {
        if (!(obj is ThreeDimensionalPoint))
            return false;
        ThreeDimensionalPoint other = (ThreeDimensionalPoint)obj;
        return this == other;
    }

    public static bool operator ==(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2)
    {
        return p1.X == p2.X && p1.Y == p2.Y && p1.Z == p2.Z;
    }

    public static bool operator !=(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2)
    {
        return !(p1 == p2);
    }
}

この構造体のインスタンスが2つある場合、それらがメモリ内の単一のデータであるか2つであるかは問題ではありません。あなたは彼らが保持する価値を気にしています。


4
トピックから少し外れましたが、objがThreeDimensionalPointでないときにArgumentExceptionをスローするのはなぜですか?その場合、単にfalseを返すべきではありませんか?
2014年

4
その通りです、私は熱望しすぎていました。return falseそこにあったはずであり、今修正中です。
AndreiRînea14年

構造体を使用する興味深い理由。GetHashCodeとEqualsを定義したクラスをここで示したものと同様に作成しましたが、辞書のキーとして使用する場合は、それらのインスタンスを変更しないように常に注意する必要がありました。おそらく私がそれらを構造体として定義していれば、より安全だったでしょう。(その場合、キーは構造体がディクショナリキーなった時点でのフィールドのコピーであるため、後でオリジナルを変更してもキーは変更されません。)
ToolmakerSteve

あなたの例では、12バイトしかないので問題ありませんが、その構造体に16バイトを超えるフィールドがたくさんある場合は、クラスを使用し、GetHashCodeメソッドとEqualsメソッドをオーバーライドすることを検討する必要があります。
Daniel Botero Correa

DDDの値型は、必ずしもC#の値型を使用する必要があるという意味ではありません
Darragh

26

Bill Wagnerは、彼の著書「effective c#」(http://www.amazon.com/Effective-Specific-Ways-Improve-Your/dp/0321245660)にこれに関する章があります。彼は次の原則を使用して結論を​​出します:

  1. タイプのデータストレージの主な責任はありますか?
  2. そのパブリックインターフェイスは、データメンバーにアクセスまたは変更するプロパティによって完全に定義されていますか?
  3. タイプにサブクラスが含まれないことを確認しますか?
  4. あなたのタイプは多態的に扱われることはないでしょうか?

4つの質問すべてに「はい」と答えた場合、構造体を使用してください。それ以外の場合は、クラスを使用します。


1
だから...データ転送オブジェクト(DTO)は構造体でなければなりませんか?
巡洋艦2011

1
上記の4つの基準を満たしている場合は、「はい」と答えます。データ転送オブジェクトを特定の方法で処理する必要があるのはなぜですか?
Bart Gijssens、2011

2
@cruizerは状況によって異なります。1つのプロジェクトでは、DTOに共通の監査フィールドがあったため、他のプロジェクトから継承されたベースDTOを作成しました。
Andrew Grothe 2013年

1
(2)以外はすべて優れた原則のように見えます。(2)によって彼が正確に何を意味するか、そしてその理由を知るために彼の推論を見る必要があるでしょう。
ToolmakerSteve

1
@ToolmakerSteve:そのためには本を読む必要があります。本の大部分をコピー/ペーストするのは公平だとは思わないでください。
Bart Gijssens


12

私は次の場合に構造体を使用します:

  1. オブジェクトは読み取り専用です(構造体を渡す/割り当てるたびに、コピーされます)。マルチスレッド処理に関しては、ほとんどの場合ロックを必要としないため、読み取り専用オブジェクトは優れています。

  2. オブジェクトは小さく、寿命が短いです。このような場合、オブジェクトがスタックに割り当てられる可能性が高く、マネージヒープにオブジェクトを置くよりもはるかに効率的です。さらに、オブジェクトによって割り当てられたメモリは、スコープの外に出るとすぐに解放されます。つまり、ガベージコレクターの方が作業が少なく、メモリがより効率的に使用されます。


10

次の場合にクラスを使用します。

  • そのアイデンティティは重要です。構造体は、値によってメソッドに渡されるときに暗黙的にコピーされます。
  • メモリーのフットプリントが大きくなります。
  • そのフィールドには初期化子が必要です。
  • 基本クラスから継承する必要があります。
  • ポリモーフィックな振る舞いが必要です。

次の場合に構造を使用します。

  • これはプリミティブ型(int、long、byteなど)のように機能します。
  • メモリフットプリントが小さい必要があります。
  • 構造体を値で渡す必要があるP / Invokeメソッドを呼び出しています。
  • アプリケーションのパフォーマンスに対するガベージコレクションの影響を減らす必要があります。
  • そのフィールドは、デフォルト値にのみ初期化する必要があります。この値は、数値型の場合はゼロ、ブール型の場合はfalse、参照型の場合はnullになります。
    • C#6.0では、構造体にデフォルトコンストラクターを含めることができ、これを使用して、構造体のフィールドを非デフォルト値に初期化できます。
  • 基本クラス(すべての構造体が継承するValueTypeを除く)から継承する必要はありません。
  • ポリモーフィックな動作は必要ありません。

5

メソッド呼び出しから物事を戻すためにいくつかの値をグループ化したいときはいつも構造体を使用していましたが、それらの値を読み取った後は何でもそれを使用する必要はありません。物事を清潔に保つ方法と同じです。私は構造体の物事を「使い捨て」として、クラスの物事をより有用で「機能的」なものとして見る傾向があります


4

エンティティが不変になる場合、構造体とクラスのどちらを使用するかという問題は、通常、セマンティクスではなくパフォーマンスの問題になります。32/64ビットシステムでは、クラス参照は、クラスの情報量に関係なく、格納するために4/8バイトを必要とします。クラス参照をコピーするには、4/8バイトをコピーする必要があります。一方、すべての異なるクラスインスタンスには、それが保持する情報とそれへの参照のメモリコストに加えて、8/16バイトのオーバーヘッドがあります。それぞれが4つの32ビット整数を保持する500エンティティの配列が必要だとします。エンティティが構造タイプである場合、500のエンティティすべてがすべて同一であるか、すべて異なるか、またはその間にあるかに関係なく、配列には8,000バイトが必要です。エンティティがクラスタイプの場合、500参照の配列には4,000バイトがかかります。これらの参照がすべて異なるオブジェクトを指している場合、オブジェクトにはそれぞれ24バイト(500全体で12,000バイト)、合計16,000バイト(構造体型のストレージコストの2倍)が必要になります。一方、1つのオブジェクトインスタンスを作成し、参照を500個の配列スロットすべてにコピーしたコードの合計コストは、そのインスタンスの24バイトと4 アレイの場合は000、合計4,024バイト。大幅な節約。最後の状況と同様にうまくいく状況はほとんどありませんが、場合によっては、そのような共有を価値のあるものにするために、いくつかの参照を十分な配列スロットにコピーすることが可能な場合があります。

エンティティが変更可能であると想定されている場合、クラスまたは構造体を使用するかどうかの問題は、いくつかの点でより簡単です。"Thing"は、xと呼ばれる整数フィールドを持つ構造体またはクラスのいずれかであり、次のコードを実行するとします。

  事t1、t2;
  ...
  t2 = t1;
  t2.x = 5;

後者のステートメントがt1.xに影響を与えたいですか?

Thingがクラス型の場合、t1とt2は同等になります。つまり、t1.xとt2.xも同等になります。したがって、2番目のステートメントはt1.xに影響します。Thingが構造タイプの場合、t1とt2は異なるインスタンスになります。つまり、t1.xとt2.xは異なる整数を参照します。したがって、2番目のステートメントはt1.xに影響しません。

.netには構造体の変異の処理にいくつかの癖がありますが、可変構造と可変クラスは根本的に異なる動作をします。値タイプの動作が必要な場合(「t2 = t1は、t1とt2を別個のインスタンスとして残しながら、t1からt2にデータをコピーすることを意味します)、. netの値タイプの処理の癖に耐えられる場合は、構造。値型のセマンティクスが必要だが、.netの癖が原因でアプリケーションの値型のセマンティクスが壊れる場合は、クラスを使用してつぶやく。


3

さらに、上記の優れた答え:

構造体は値型です。

それらをNothingに設定することはできませ

構造= Nothingを設定すると、すべての値タイプがデフォルト値に設定されます。


2

実際には動作は必要ないが、単純な配列や辞書よりも多くの構造が必要な場合。

フォローアップ これは、構造体の一般的な考え方です。私は彼らが方法を持つことができることを知っていますが、私はその全体的な精神的区別を保つのが好きです。


どうしてそんなこと言うの?構造体はメソッドを持つことができます。
エステバンアラヤ

2

@Simonが言ったように、構造体は「値型」のセマンティクスを提供するため、組み込みのデータ型と同様の動作が必要な場合は、構造体を使用します。構造体はコピーによって渡されるため、サイズが約16バイトであることを確認する必要があります。


2

これは古いトピックですが、簡単なベンチマークテストを提供する必要がありました。

2つの.csファイルを作成しました。

public class TestClass
{
    public long ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

そして

public struct TestStruct
{
    public long ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

ベンチマークを実行します。

  • 1つのTestClassを作成する
  • 1つのTestStructを作成する
  • 100個のTestClassを作成する
  • 100個のTestStructを作成
  • 10000テストクラスを作成
  • 10000 TestStructを作成する

結果:

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
Intel Core i5-8250U CPU 1.60GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.101
[Host]     : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT  [AttachedDebugger]
DefaultJob : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT


|         Method |           Mean |         Error |        StdDev |     Ratio | RatioSD | Rank |    Gen 0 | Gen 1 | Gen 2 | Allocated |
|--------------- |---------------:|--------------:|--------------:|----------:|--------:|-----:|---------:|------:|------:|----------:|

|      UseStruct |      0.0000 ns |     0.0000 ns |     0.0000 ns |     0.000 |    0.00 |    1 |        - |     - |     - |         - |
|       UseClass |      8.1425 ns |     0.1873 ns |     0.1839 ns |     1.000 |    0.00 |    2 |   0.0127 |     - |     - |      40 B |
|   Use100Struct |     36.9359 ns |     0.4026 ns |     0.3569 ns |     4.548 |    0.12 |    3 |        - |     - |     - |         - |
|    Use100Class |    759.3495 ns |    14.8029 ns |    17.0471 ns |    93.144 |    3.24 |    4 |   1.2751 |     - |     - |    4000 B |
| Use10000Struct |  3,002.1976 ns |    25.4853 ns |    22.5920 ns |   369.664 |    8.91 |    5 |        - |     - |     - |         - |
|  Use10000Class | 76,529.2751 ns | 1,570.9425 ns | 2,667.5795 ns | 9,440.182 |  346.76 |    6 | 127.4414 |     - |     - |  400000 B |

1

うーん...

私は、構造体とクラスの使用に対する引数としてガベージコレクションを使用しません。マネージヒープはスタックのように機能します。オブジェクトを作成すると、ヒープの一番上に配置されます。これは、スタックへの割り当てとほぼ同じ速さです。さらに、オブジェクトの存続期間が短く、GCサイクルに耐えられない場合、GCはまだアクセス可能なメモリでのみ機能するため、割り当て解除は無料です。(MSDNを検索してください。.NETのメモリ管理に関する一連の記事があります。私はそれらを探すのが面倒です)。

ほとんどの場合、構造体を使用しますが、参照セマンティクスを使用することで物事が少し簡単になることを後で発見したので、そうするために自分を蹴り散らしています。

とにかく、上記のMSDN記事の4つのポイントは、良いガイドラインのようです。


1
構造体による参照セマンティクスが必要な場合は、単にを宣言するclass MutableHolder<T> { public T Value; MutableHolder(T value) {Value = value;} }と、a MutableHolder<T>は変更可能なクラスセマンティクスを持つオブジェクトになります(これTは、構造体または不変のクラス型の場合も同様に機能します)。
スーパーキャット2013

1

構造体はヒープではなくスタック上にあるため、スレッドセーフであり、転送オブジェクトパターンを実装するときに使用する必要があります。揮発性であるヒープ上でオブジェクトを使用することはありません。この場合は、コールスタックを使用します。これは、構造体を使用するための基本的なケースです。


-3

最良の答えは、必要なのがプロパティのコレクションである場合は単にstructを使用することであり、プロパティと動作のコレクションである場合はクラスを使用することだと思います。


構造体もメソッドを持つことができます
Nithin Chandran

もちろん、メソッドが必要な場合は、99%の確率でクラスではなく構造体を不適切に使用しています。structにメソッドを配置しても問題がないときに私が見つけた唯一の例外は、コールバックです
Lucian Gabriel Popescu
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.