構造体の中にクラスを作ることはできるのでしょうか?
-
09-06-2019 - |
質問
C# では、クラス型であるメンバー変数を持つ構造体を持つことは可能ですか?もしそうなら、情報はスタック、ヒープ、またはその両方のどこに保存されますか?
解決
はい、できます。クラスのメンバ変数へのポインタが格納されます。 スタックの上に 残りの構造体の値とともにクラス インスタンスのデータがヒープに保存されます。
構造体には、クラス定義をメンバー (内部クラス) として含めることもできます。
以下は、それが可能であることを示すために、少なくともコンパイルして実行できる、本当に役に立たないコードです。
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
MyStr m = new MyStr();
m.Foo();
MyStr.MyStrInner mi = new MyStr.MyStrInner();
mi.Bar();
Console.ReadLine();
}
}
public class Myclass
{
public int a;
}
struct MyStr
{
Myclass mc;
public void Foo()
{
mc = new Myclass();
mc.a = 1;
}
public class MyStrInner
{
string x = "abc";
public string Bar()
{
return x;
}
}
}
}
他のヒント
クラスのコンテンツはヒープに保存されます。
クラスへの参照 (ポインターとほぼ同じ) は、構造体のコンテンツとともに保存されます。構造体のコンテンツが保存される場所は、それがローカル変数、メソッド パラメーター、またはクラスのメンバーであるかどうか、およびボックス化されているかクロージャーによってキャプチャされているかによって異なります。
構造体のフィールドの 1 つがクラス型である場合、そのフィールドは次のいずれかを保持します。 身元 クラスオブジェクトの場合、または null 参照の場合。問題のクラスオブジェクトが不変の場合 (例: string
)、その ID を保存すると、その内容も事実上保存されます。ただし、問題のクラス オブジェクトが変更可能な場合は、ID を保存することがコンテンツを保存する効果的な手段になります。 参照がフィールドに格納された後に変更される可能性のあるコードの手に渡らない場合に限ります。.
一般に、次の 2 つの状況のいずれかが当てはまらない限り、可変クラス型を構造体内に格納することは避けるべきです。
- 実際、関心があるのは、クラス オブジェクトの内容ではなく、そのアイデンティティです。たとえば、後でコントロールを復元できるようにするために、タイプ `Control` および `Rectangle` のフィールドを保持し、ある時点でコントロールが持っていた `Bounds` を表す `FormerControlBounds` 構造を定義できます。前の位置に戻ります。「Control」フィールドの目的は、コントロールの状態のコピーを保持することではなく、位置を復元する必要があるコントロールを識別することです。一般に、構造体は、そのようなアクセスが問題のオブジェクトの現在の変更可能な状態を参照していることが明らかな場合を除き、参照を保持しているオブジェクトの変更可能なメンバーへのアクセスを避ける必要があります (例:`CaptureControlPosition` または `RestoreControlToCapturedPosition` メソッド、あるいは `ControlHasMoved` プロパティ内)。
- フィールドは「プライベート」であり、フィールドを読み取る唯一のメソッドは、オブジェクト自体を外部コードに公開せずにそのプロパティを調べる目的で読み取りを行い、フィールドを書き込む唯一のメソッドは、新しいオブジェクトを作成し、すべての変更を実行します。今後発生する可能性があることを確認し、そのオブジェクトへの参照を保存します。たとえば、構造体にプライベート フィールドに配列を保持させ、配列に書き込もうとするたびにデータを含む新しい配列を作成することで、配列とよく似た動作をするが、値セマンティクスを持つ「構造体」を設計することができます。古い配列から新しい配列を変更し、変更した配列をそのフィールドに保存します。配列自体は変更可能な型であっても、フィールドに格納されるすべての配列インスタンスは、それを変更する可能性のあるコードからアクセスできないため、事実上不変であることに注意してください。
シナリオ #1 はジェネリック型では非常に一般的なものであることに注意してください。たとえば、「値」が可変オブジェクトの ID である辞書があることは非常に一般的です。その辞書を列挙すると、次のインスタンスが返されます。 KeyValuePair
だれの Value
フィールドにはその可変型が保持されます。
シナリオ #2 はあまり一般的ではありません。残念ながら、プロパティ セッター以外の構造体メソッドが構造体を変更するため、読み取り専用コンテキストではその使用を禁止する必要があることをコンパイラーに伝える方法はありません。次のように動作する構造体を持つこともできます。 List<T>
, 、ただし値セマンティクスを備えており、 Add
メソッドですが、呼び出そうとしています Add
読み取り専用の構造体インスタンスでは、コンパイラ エラーではなく偽のコードが生成されます。さらに、そのような構造体のメソッドやプロパティ セッターを変更すると、一般にパフォーマンスがかなり低下します。このような構造体は、本来は変更可能なクラスの不変更ラッパーとして存在する場合に役立ちます。このような構造体がボックス化されない場合、パフォーマンスはクラスよりも優れていることがよくあります。ちょうど 1 回ボックス化された場合 (例:インターフェイス型にキャストされることにより)、パフォーマンスは通常、クラスと同等になります。繰り返しボックス化すると、パフォーマンスがクラスよりも大幅に低下する可能性があります。
おそらく、これを行うことは推奨されません。見る http://msdn.microsoft.com/en-us/library/ms229017(VS.85).aspx
参照タイプはヒープに割り当てられ、メモリ管理はガベージコレクターによって処理されます。
値タイプはスタックまたはインラインで割り当てられ、範囲外に出ると扱われます。
一般に、値型は割り当てと割り当て解除のコストが低くなります。ただし、かなりの量のボクシングとボックス化を必要とするシナリオで使用されている場合、参照タイプと比較してパフォーマンスが低下します。