いつクラスの代わりに構造体を使用しますか?[閉まっている]


174

構造体とクラスを使用する場合の経験則は何ですか?私はそれらの用語のC#定義を考えていますが、あなたの言語が同様の概念を持っているなら、あなたの意見も聞きたいです。

私はほとんどすべてにクラスを使用する傾向があり、構造が非常に単純化されており、PhoneNumberなどのような値型でなければならない場合にのみ構造体を使用します。しかし、これは比較的マイナーな使用のように思われ、より興味深いユースケースがあることを願っています。



7
@Frustrated別のサイトで何かの複製として質問をマークすることはできませんが、それは確かに関連しています。
アダムリア

@Anna Lear:私は知っています、私は重複するように移行しないことに投票しました。移行したら、適切に複製として閉じることができます
FrustratedWithFormsDesigner

5
@Frustrated移行する必要はないと思います。
アダムリア

15
これは、P.SE対SOにはるかに適した質問のようです。それが私がここでそれを尋ねた理由です。
RationalGeek

回答:


169

従うべき一般的な規則は、構造体は関連するプロパティの小さくシンプルな(1レベルの)コレクションでなければならず、作成後は不変であるということです。それ以外の場合は、クラスを使用します。

C#には、構造体とクラスに定義キーワード以外の宣言の明確な違いがないという点で優れています。そのため、構造体をクラスに「アップグレード」する必要がある、または逆にクラスを構造体に「ダウングレード」する必要があると感じた場合、ほとんどの場合、キーワードを変更するだけです(他にもいくつかの落とし穴があります;構造体は導出できません他のクラスまたは構造体型から、デフォルトのパラメータなしのコンストラクタを明示的に定義することはできません)。

構造体について知っておくべきより重要なことは、それらが値型であるため、クラス(参照型)のように扱うと痛みが半分になるということです。特に、構造のプロパティを変更可能にすると、予期しない動作が発生する可能性があります。

たとえば、AとBという2つのプロパティを持つクラスSimpleClassがあるとします。このクラスのコピーをインスタンス化し、AとBを初期化してから、インスタンスを別のメソッドに渡します。このメソッドはさらにAとBを変更します。呼び出し元の関数(インスタンスを作成した関数)に戻ると、インスタンスのAとBには、呼び出されたメソッドによって値が与えられます。

今、あなたはそれを構造体にします。プロパティは変更可能です。前と同じ構文で同じ操作を実行しますが、メソッドを呼び出した後のAとBの新しい値はインスタンスにありません。どうした?さて、あなたのクラスは構造体になりました。つまり、値型です。メソッドに値タイプを渡す場合、デフォルトでは(outまたはrefキーワードなしで)「値渡し」が渡されます。インスタンスの浅いコピーがメソッドで使用するために作成され、メソッドが終了して最初のインスタンスをそのまま残したときに破棄されます。

参照型を構造体のメンバーとして使用する場合、これはさらに混乱を招きます(許可されていませんが、実質的にすべての場合で非常に悪い習慣です)。クラスは複製されないため(構造体への参照のみ)、​​構造体への変更は元のオブジェクトには影響しませんが、構造体のサブクラスへの変更は呼び出し元コードからのインスタンスに影響します。これにより、変更可能な構造体が非常に一貫性のない状態になり、実際の問題のある場所から遠く離れた場所でエラーが発生する可能性が非常に高くなります。

このため、C#のほぼすべての機関は、常に構造を不変にすることを推奨しています。消費者がオブジェクトの構築時にのみプロパティの値を指定できるようにし、そのインスタンスの値を変更する手段を提供しないでください。読み取り専用フィールド、または取得専用プロパティがルールです。消費者が値を変更する場合、必要な変更を加えて、古いオブジェクトの値に基づいて新しいオブジェクトを作成するか、同じことを行うメソッドを呼び出すことができます。これにより、構造体の単一のインスタンスを、他のすべてと区別できない(ただし、場合によっては平等な)1つの概念的な「値」として扱うように強制されます。タイプによって保存された「値」に対して操作を実行すると、初期値とは異なる新しい「値」を取得します。

良い例として、DateTimeタイプを見てください。DateTimeインスタンスのフィールドを直接割り当てることはできません。新しいインスタンスを作成するか、新しいインスタンスを生成する既存のメソッドを呼び出す必要があります。これは、日付と時刻が数字の5のように「値」であり、数字の5を変更すると5ではない新しい値になるためです。5+ 1 = 6だからといって、5が6になるわけではありません1を追加したからです。DateTimesも同じように機能します。12:00は12:01に「なる」ことはありませんが、分を追加すると、代わりに12:00とは異なる新しい値12:01を取得します。これがあなたのタイプの論理的な状態である場合(.NETに組み込まれていない良い概念的な例は、お金、距離、重量、および操作が値のすべての部分を考慮する必要があるUOMのその他の量です)、次に、構造体を使用し、それに応じて設計します。オブジェクトのサブアイテムが独立して変更可能である必要がある他のほとんどの場合、クラスを使用します。


1
いい答えだ!「ドメイン駆動型開発」の方法論と本を読んでください。実際に実装しようとしている概念に基づいてクラスまたは構造を使用する理由についてのあなたの意見に関連すると思われます
Maxim Popravko

1
言及する価値があるほんの少しの詳細:構造体に独自のデフォルトコンストラクターを定義することはできません。すべてをデフォルト値に初期化するもので間に合わせる必要があります。通常、それはかなりマイナーです。特にクラスでは、構造体の候補として適しています。ただし、クラスを構造体に変更することは、単にキーワードを変更するだけではないことを意味します。
ローランブルゴーロイ

1
@ LaurentBourgault-Roy-はい。他にも落とし穴があります。たとえば、構造体に継承階層を含めることはできません。インターフェイスを実装できますが、System.ValueType以外から派生することはできません(構造体であることにより暗黙的に)。
キース

JavaScript開発者として、2つのことを尋ねなければなりません。構造体の一般的な使用法とは異なるオブジェクトリテラルはどのようになっていますか?また、構造体が不変であることが重要なのはなぜですか?
エリックReppen

1
@ErikReppen:両方の質問への答え:構造体が関数に渡されると、値で渡されます。クラスを使用すると、クラスへの参照を渡すことができます(値による)。Eric Lippertが、なぜこれが問題なのかを議論しています:blogs.msdn.com/b/ericlippert/archive/2008/05/14/…。KeithSの最後の段落には、これらの質問も含まれています。
ブライアン

14

答え:「純粋なデータ構造に構造体を使用し、オペレーションを持つオブジェクトにクラスを使用する」は間違いなく間違ったIMOです。構造体が多数のプロパティを保持している場合、クラスはほぼ常に適切です。Microsoftは、効率の観点から、タイプが16バイトを超える場合はクラスである必要があるとよく言います。

https://stackoverflow.com/questions/1082311/why-should-a-net-struct-be-less-than-16-bytes


1
絶対に。できれば賛成です。構造体はデータ用であり、オブジェクトはそうではないという考えがどこから来たのかわかりません。C#の構造体は、スタックに割り当てるためのメカニズムにすぎません。オブジェクトポインタのコピーだけでなく、構造体全体のコピーが渡されます。
mike30

2
@mike-C#構造体は必ずしもスタックに割り当てられるわけではありません。
リー

1
@mike「構造体はデータ用」という考えは、おそらく長年のCプログラミングで習得された習慣に由来しています。Cにはクラスがなく、その構造体はデータ専用のコンテナーです。
コナミマン

明らかに、マイケルKの答えを読んだ(賛成になった)少なくとも3人のCプログラマがいます;
bytedev

10

a classとa の最も重要な違いstructは、次の状況で何が起こるかです。

  Thing thing1 = new Thing();
  thing1.somePropertyOrField = 5;
  Thing thing2 = thing1;
  thing2.somePropertyOrField = 9;

上の最後のステートメントの効果は何thing1.somePropertyOrFieldですか?もしThing構造体、およびsomePropertyOrField暴露公共の場、オブジェクトがあるthing1thing2お互いから「デタッチ」となりますので、後者のステートメントは影響しませんthing1。場合はThingクラスであり、その後、thing1そしてthing2お互いに添付されます、そしてので後者のステートメントが記述しますthing1.somePropertyOrField。前者のセマンティクスがより理にかなっている場合は構造体を使用し、後者のセマンティクスがより理にかなっている場合はクラスを使用する必要があります。

何かを可変にしたいという願望は、クラスであることを支持する議論であると忠告する人もいますが、私はその逆が真実であることを提案します:データを保持する目的で存在するものが可変になる場合インスタンスが何かにアタッチされているかどうかが明らかでない場合、インスタンスは他の何かにアタッチされていないことを明確にするために、構造体(公開フィールドの可能性が高い)である必要があります。

たとえば、次のステートメントを考えます。

  Person somePerson = myPeople.GetPerson( "123-45-6789");
  somePerson.Name = "Mary Johnson"; //「Mary Smith」だった

2番目のステートメントは、に格納されている情報を変更しmyPeopleますか?if Personが公開フィールド構造体である場合、そうではなく、それが公開フィールド構造体であることの明らかな結果になります。Person構造体であり、更新したい場合は、myPeople次のようなことを明確に行う必要がありますmyPeople.UpdatePerson("123-45-6789", somePerson)Personただし、クラスがの場合、上記のコードがのコンテンツを更新しないかMyPeople、常に更新するか、更新するかどうかを判断するのははるかに困難です。

構造体は「不変」であるという概念に関しては、一般的には同意しません。「不変」構造体(コンストラクターに不変条件が適用される)には有効な使用例がありますが、構造体の一部が変更されるたびに構造体全体を書き換える必要があるため、単純に公開するよりもバグが発生しやすくなりますフィールドを直接。たとえばPhoneNumber、フィールド、他のものを含む、AreaCodeおよびの構造体を考えExchangeてくださいList<PhoneNumber>。以下の効果はかなり明確になるはずです。

  for(int i = 0; i <myList.Count; i ++)
  {
    PhoneNumber theNumber = myList [i];
    if(theNumber.AreaCode == "312")
    {
      string newExchange = "";
      if(new312to708Exchanges.TryGetValue(theNumber.Exchange)、out newExchange)
      {
        theNumber.AreaCode = "708";
        theNumber.Exchange = newExchange;
        myList [i] = theNumber;
      }
    }
  }

上記のコードではPhoneNumberAreaCodeand 以外のフィールドを認識したり、気にしたりしないことに注意してくださいExchange。場合はPhoneNumber、いわゆる「不変」構造体だった、それが必要であろういずれかでそれが実現することをwithXX示されたフィールドに渡された値を開催し、新たな構造体のインスタンスを返しますか、あるいはそれが必要となる各フィールドのための方法を、上記のようなコードでは、構造体のすべてのフィールドについて知ることができます。必ずしも魅力的ではありません。

ところで、構造体が可変型への参照を合理的に保持できる場合が少なくとも2つあります。

  1. 構造体のセマンティクスは、オブジェクトのプロパティを保持するためのショートカットとしてではなく、問題のオブジェクトのIDを格納していることを示しています。たとえば、 `KeyValuePair`は特定のボタンのIDを保持しますが、それらのボタンの位置、ハイライト状態などに関する永続的な情報を保持することは期待されません。
  2. 構造体は、そのオブジェクトへの唯一の参照を保持していること、他の誰も参照を取得しないこと、およびそのオブジェクトへの参照がどこにでも格納される前に実行されるすべての変更が行われたことを知っています。

前者のシナリオでは、オブジェクトのIDENTITYは不変です。第二に、ネストされたオブジェクトのクラスは不変性を強制しないかもしれませんが、参照を保持する構造体はそうします。


7

1つのメソッドが必要な場合にのみ構造体を使用する傾向があるため、クラスを「重く」見えるようにします。したがって、構造体を軽量オブジェクトとして扱うことがあります。注意:これは常に機能するとは限らず、常に最善のこととは限らないため、状況によって異なります。

追加リソース

より正式な説明については、MSDNは次の ように述べています。http : //msdn.microsoft.com/en-us/library/ms229017.aspxこのリンクは、上記で説明した内容を検証します。構造体は、少量のデータを格納するためにのみ使用してください。


5
別のサイトの質問(そのサイトがStack Overflowであっても)は重複としてカウントされません。他の場所へのリンクだけでなく、実際の回答が含まれるように回答を展開してください。リンクが変更されると、今書いているとおりの答えは役に立たなくなり、情報を保存し、プログラマを可能な限り自己完結させたいと考えています。ありがとう!
アダムリア

2
@Anna Lear教えてくれてありがとう、これからも心に留めておいてください!
11

2
-1「1つのメソッドが必要な場合にのみ構造体を使用する傾向があるため、クラスを「重く」見えるようにします。」...重い?それは本当にどういう意味ですか?...そして、cosにはメソッドが1つしかなく、おそらく構造体であると本当に言っているのですか?
bytedev 16

2

純粋なデータ構造体には構造体を使用し、操作を持つオブジェクトにはクラスを使用します。

データ構造にアクセス制御が不要で、get / set以外の特別な操作がない場合は、構造体を使用します。これにより、その構造がすべてデータのコンテナであることが明らかになります。

構造に、より複雑な方法で構造を変更する操作がある場合は、クラスを使用します。クラスは、メソッドへの引数を介して他のクラスと対話することもできます。

レンガと飛行機の違いと考えてください。レンガは構造体です。長さ、幅、高さがあります。それはあまりできません。飛行機には、操縦翼面の移動やエンジン推力の変更など、何らかの形で航空機を変化させる複数の操作があります。クラスとしては良いでしょう。


2
「純粋なデータ構造に構造体を使用し、操作を持つオブジェクトにクラスを使用する」C#ではこれはナンセンスです。以下の私の答えをご覧ください。
bytedev 14年

1
「純粋なデータ構造には構造体を使用し、操作を伴うオブジェクトにはクラスを使用します。」これはC#ではなくC / C ++に当てはまります
-taktak004
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.