回答:
MSDNには答えがあります: クラスと構造の選択。
基本的に、そのページには4項目のチェックリストがあり、タイプがすべての基準を満たさない限り、クラスを使用するように指示されています。
タイプに次のすべての特性がない限り、構造を定義しないでください。
- これは、プリミティブ型(整数、ダブルなど)と同様に、単一の値を論理的に表します。
- インスタンスサイズは16バイト未満です。
- それは不変です。
- 頻繁にボックス化する必要はありません。
ref
そうすることが合理的であるときはいつでも、それらをパラメーターとして渡します。4,000のフィールドを持つ構造体をrefパラメーターとしてメソッドに渡すと、変更されたバージョンを返すメソッドに値によって4つのフィールドを持つ構造体を渡すよりも安上がりになります。
これまでの回答でこれを読んでいないことに驚いています。これは最も重要な側面だと思います。
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つであるかは問題ではありません。あなたは彼らが保持する価値を気にしています。
return false
そこにあったはずであり、今修正中です。
Bill Wagnerは、彼の著書「effective c#」(http://www.amazon.com/Effective-Specific-Ways-Improve-Your/dp/0321245660)にこれに関する章があります。彼は次の原則を使用して結論を出します:
- タイプのデータストレージの主な責任はありますか?
- そのパブリックインターフェイスは、データメンバーにアクセスまたは変更するプロパティによって完全に定義されていますか?
- タイプにサブクラスが含まれないことを確認しますか?
- あなたのタイプは多態的に扱われることはないでしょうか?
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の癖が原因でアプリケーションの値型のセマンティクスが壊れる場合は、クラスを使用してつぶやく。
さらに、上記の優れた答え:
構造体は値型です。
それらをNothingに設定することはできません。
構造= Nothingを設定すると、すべての値タイプがデフォルト値に設定されます。
これは古いトピックですが、簡単なベンチマークテストを提供する必要がありました。
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; }
}
ベンチマークを実行します。
結果:
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 |
うーん...
私は、構造体とクラスの使用に対する引数としてガベージコレクションを使用しません。マネージヒープはスタックのように機能します。オブジェクトを作成すると、ヒープの一番上に配置されます。これは、スタックへの割り当てとほぼ同じ速さです。さらに、オブジェクトの存続期間が短く、GCサイクルに耐えられない場合、GCはまだアクセス可能なメモリでのみ機能するため、割り当て解除は無料です。(MSDNを検索してください。.NETのメモリ管理に関する一連の記事があります。私はそれらを探すのが面倒です)。
ほとんどの場合、構造体を使用しますが、参照セマンティクスを使用することで物事が少し簡単になることを後で発見したので、そうするために自分を蹴り散らしています。
とにかく、上記のMSDN記事の4つのポイントは、良いガイドラインのようです。
class MutableHolder<T> { public T Value; MutableHolder(T value) {Value = value;} }
と、a MutableHolder<T>
は変更可能なクラスセマンティクスを持つオブジェクトになります(これT
は、構造体または不変のクラス型の場合も同様に機能します)。
最良の答えは、必要なのがプロパティのコレクションである場合は単にstructを使用することであり、プロパティと動作のコレクションである場合はクラスを使用することだと思います。