web-dev-qa-db-ja.com

クラスではなく構造体をいつ使用しますか?

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

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

176
RationalGeek

従うべき一般的な規則は、構造体は関連するプロパティの小さく単純な(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を加えたので。 DateTimeも同じように機能します。 12:00は、12:01に「にならない」分を追加した場合、代わりに12:00とは異なる新しい値12:01を取得します。これがあなたのタイプの論理的な状態である場合(.NETに組み込まれていない良い概念的な例は、操作が値のすべての部分を考慮する必要があるUOMのMoney、Distance、Weight、およびその他の数量です)、次に、構造体を使用し、それに応じて設計します。オブジェクトのサブアイテムが個別に変更可能である必要がある他のほとんどの場合、クラスを使用します。

170
KeithS

答え:「純粋なデータ構造には構造体を使用し、操作のあるオブジェクトにはクラスを使用する」は間違いなくIMOです。構造体が多数のプロパティを保持している場合は、ほとんどの場合、クラスがより適切です。 Microsoftは、効率の観点から、タイプが16バイトより大きい場合はクラスであるとよく言っています。

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

14
bytedev

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

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

最後のステートメントが_thing1.somePropertyOrField_に与える影響は何ですか? Thingが構造体であり、somePropertyOrFieldが公開されたパブリックフィールドである場合、オブジェクト_thing1_と_thing2_は互いに「切り離される」ため、後者のステートメントは_thing1_には影響しません。 Thingがクラスの場合、_thing1_と_thing2_は互いに接続されるため、後者のステートメントは_thing1.somePropertyOrField_を書き込みます。前者のセマンティクスがより意味のある場合は構造体を使用し、後者のセマンティクスがより意味のある場合はクラスを使用する必要があります。

一部の人々は、何かを変更可能にしたいという欲求はクラスであることを支持する議論であるとアドバイスしますが、私はその逆が真実であることを示唆します:データを保持する目的で存在する何かが変更可能になる場合、インスタンスが何かに接続されているかどうかが明らかでない場合、インスタンスは他に接続されていないことを明確にするために、(おそらく公開されたフィールドを持つ)構造体である必要があります。

たとえば、次のステートメントについて考えます。

 Person somePerson = myPeople.GetPerson( "123-45-6789"); 
 somePerson.Name = "メアリージョンソン"; //「Mary Smith」だった

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

構造体は「不変」である必要があるという概念に関して、私は一般的に反対します。 「不変の」構造体には有効な使用例がありますが(コンストラクターで不変式が適用されます)、構造体の一部が変更されたときに不便で無駄が多く、単純に公開するよりも構造体全体を書き直す必要があります。フィールドを直接。たとえば、フィールドにPhoneNumberAreaCodeが含まれる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; 
} 
} 
 } 

上記のコードでは、PhoneNumberAreaCode以外のExchangeのフィールドについて何も認識していないことに注意してください。 PhoneNumberがいわゆる「不変の」構造体である場合、各フィールドにwithXXメソッドを提供する必要があります。これにより、渡された新しい構造体インスタンスが返されます指定されたフィールドの値、または上記のようなコードが構造体のすべてのフィールドについて知る必要があります。あまり魅力的ではありません。

ところで、構造体が変更可能な型への参照を合理的に保持しているケースが少なくとも2つあります。

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

前者のシナリオでは、オブジェクトのIDENTITYは不変です。 2番目の例では、ネストされたオブジェクトのクラスは不変性を強制しませんが、参照を保持する構造体は強制します。

10
supercat

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

追加のリソース

より正式な説明については、MSDNは次のように述べています: http://msdn.Microsoft.com/en-us/library/ms229017.aspx このリンクは、上記のように、構造体は少量のデータを格納するためにのみ使用する必要があります。

7
rrazd

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

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

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

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

2
Michael K