16

主な質問は、有用性とメモリに関して、このキーワードを変更できるようにすることの意味は何ですか。そして、なぜこれがC#言語仕様で許可されているのですか?

他の質問/サブパートは、そうすることを選択した場合、答えられるかどうかはわかりません。それらへの回答は、主要な質問への回答を明確にするのに役立つと思いました。

私はあなたがC#または.NETで見た中で最も奇妙なコーナーケースは何ですか?への答えとしてこれに出くわしました。

public struct Teaser
{
    public void Foo()
    {
        this = new Teaser();
    }
}

私は、C#言語仕様でこれが許可される理由に頭を悩ませようとしてきました。 サブパート1これを変更可能にすることを正当化するものはありますか?それはすべて役に立ちますか?

その答えに対するコメントの1つは

CLRからC#経由:これを作成した理由は、別のコンストラクターで構造体のパラメーターなしのコンストラクターを呼び出すことができるためです。構造体の1つの値のみを初期化し、他の値をゼロ/ null(デフォルト)にしたい場合は、public Foo(int bar){this = new Foo();と書くことができます。specialVar=bar;}。これは効率的ではなく、実際には正当化されません(specialVarは2回割り当てられます)が、参考までに。(それが本に記載されている理由です。なぜ私たちは公開Foo(int bar)を行うべきではないのかわかりません:this())

サブパート2。 私はその推論に従うかどうかわかりません。誰かが彼の意味を明確にすることができますか?多分それがどのように使われるかについての具体的な例?

編集(スタックまたはヒープの主なポイントは、メモリの解放またはガベージコレクションに関するものです。int[]の代わりに、262144のパブリックintフィールドに置き換えることができます)また、私の理解では、構造体はヒープではなくスタックに作成されます。この構造体では、1Mbバイトの配列フィールドが次のように初期化されます。

public int[] Mb = new int[262144];

サブパート3.Fooが呼び出されたときに、これがスタックから削除されることはありますか?私には、構造体がスコープから外れることはなかったので、スタックから削除されないようです。今夜はテストケースを作成する時間がありませんが、明日はテストケースを作成するかもしれません。

以下のコードでは

Teaser t1 = new Teaser();
Teaser tPlaceHolder = t1;
t1.Foo();

サブパート4。t1とtPlaceHolderは同じまたは異なるアドレス空間を占めていますか?

3年前の投稿を表示して申し訳ありませんが、これは本当に頭を悩ませています。

参考までに、stackoverflowに関する最初の質問です。質問に問題があった場合は、コメントを投稿して編集します。

2日後、質問を説明するために答えにはかなりの量の作業が必要になると思うので、すでに勝者が頭に浮かんだとしても、この質問に50の賞金をかけます。

4

3 に答える 3

12

まず、正しい質問をしているかどうかを調べることから始めるべきだと思います。おそらく、「C# では構造体への代入が許可されないのはなぜですか?」と尋ねる必要があります。this

参照型のキーワードへの割り当てthisは潜在的に危険です。実行中のメソッドのオブジェクトへの参照を上書きしています。その参照を初期化しているコンストラクター内でそうすることさえできます。その振る舞いがどうあるべきかは明らかではありません。それを理解する必要を避けるために、それは一般的に有用ではないため、仕様 (またはコンパイラ) によって許可されていません。

thisただし、値型のキーワードへの割り当ては明確に定義されています。値型の割り当てはコピー操作です。各フィールドの値は、割り当ての右側から左側に再帰的にコピーされます。これは、構造体の元のコピーがまだ存在するため、コンストラクター内であっても、構造体に対する完全に安全な操作であり、そのデータを変更しているだけです。これは、構造体の各フィールドを手動で設定するのとまったく同じです。仕様またはコンパイラが、明確に定義された安全な操作を禁止する必要があるのはなぜですか?

ちなみに、これはあなたのサブ質問の1つに答えます。値の型の割り当ては、参照コピーではなくディープ コピー操作です。このコードを考えると:

Teaser t1 = new Teaser();
Teaser tPlaceHolder = t1;
t1.Foo();

構造体の 2 つのコピーを割り当てTeaser、最初のフィールドの値を 2 番目のフィールドにコピーしました。これが値型の性質です。同じフィールドを持つ 2 つの型は同じです。ちょうどint、どちらも 10 を含む 2 つの変数は、"メモリ内" の場所に関係なく同じです。

また、これは重要であり、繰り返す価値があります。「スタック」と「ヒープ」で何が起こるかについて慎重に仮定します。値型は、使用されるコンテキストに応じて、常にヒープに配置されます。クローズされていない、またはスコープ外に持ち上げられていない、有効期間が短い (ローカル スコープの) 構造体は、スタックに割り当てられる可能性が非常に高くなります。しかし、これは重要ではない実装の詳細であり、気にしたり、依存したりすべきではありません。重要なのは、それらが値型であり、そのように動作することです。

への割り当てが実際にどれほど役立つかという限りでは、thisそれほどではありません。具体的なユースケースについてはすでに言及されています。これを使用して、構造体をデフォルト値でほとんど初期化できますが、指定する数値は小さくなります。コンストラクターが戻る前にすべてのフィールドを設定する必要があるため、これにより多くの冗長なコードを節約できます。

public struct Foo
{
  // Fields etc here.

  public Foo(int a)
  {
    this = new Foo();
    this.a = a;
  }
}

クイックスワップ操作を実行するためにも使用できます。

public void SwapValues(MyStruct other)
{
  var temp = other;
  other = this;
  this = temp;
}

それを超えて、それは言語の興味深い副作用であり、構造と値の型が実装される方法であり、おそらく知る必要はありません.

于 2012-04-06T04:09:16.110 に答える
1

これを割り当て可能にすることで、構造体を使用した「高度な」コーナー ケースが可能になります。私が見つけた一例は、スワップメソッドでした:

struct Foo 
{
    void Swap(ref Foo other)
    {
         Foo temp = this;
         this = other;
         other = temp;
    }
}

不変である構造体のデフォルトの「望ましい」性質に違反するため、この使用には強く反対します。このオプションを使用する理由は、ほぼ間違いなく不明です。

さて、構造体自体についてです。クラスとはいくつかの点で異なります。

  • それらはマネージド ヒープではなく、スタック上に存在できます。
  • アンマネージ コードにマーシャリングすることができます。
  • NULL 値に割り当てることはできません。

完全な概要については、http ://www.jaggersoft.com/pubs/StructsVsClasses.htm を参照してください。

あなたの質問に関連して、構造体がスタックまたはヒープのどちらにあるかです。これは、構造体の配置場所によって決まります。構造体がクラスのメンバーである場合、ヒープに割り当てられます。それ以外の場合、構造体が直接割り当てられる場合は、ヒープに割り当てられます (実際には、これは全体像の一部にすぎません。C# 2.0 で導入されたクロージャーについて話し始めると、全体がかなり複雑になりますが、今のところはそれで十分です。あなたの質問に答えてください)。

.NET の配列は、デフォルトでヒープに割り当てられます (この動作は、安全でないコードと stackalloc キーワードを使用する場合には一貫性がありません)。上記の説明に戻ると、構造体インスタンスもヒープに割り当てられることを示しています。実際、これを証明する簡単な方法は、サイズが 1 MB の配列を割り当て、スタックオーバーフロー例外がスローされないことを観察することです。

スタック上のインスタンスの有効期間は、そのスコープによって決まります。これは、ガベージ コレクターによって有効期間が決定されるマネージャー ヒープ上のインスタンス (およびそのインスタンスへの参照がまだあるかどうか) とは異なります。スコープ内にある限り、スタック上のすべてのものが存続することを保証できます。スタックにインスタンスを割り当ててメソッドを呼び出しても、そのインスタンスがスコープ外になるまで (デフォルトでは、そのインスタンスが宣言されたメソッドが終了するまで) そのインスタンスの割り当ては解除されません。

構造体はそれに対するマネージ参照を持つことができません (ポインターはアンマネージ コードで可能です)。C# でスタック上の構造体を操作する場合、基本的に、参照ではなくインスタンスに対するラベルがあります。ある構造体を別の構造体に割り当てると、基になるデータがコピーされるだけです。参照は構造体として見ることができます。単純に言えば、参照は、メモリ内の特定の部分へのポインターを含む構造体にすぎません。ある参照を別の参照に割り当てると、ポインター データがコピーされます。

// declare 2 references to instances on the managed heap
var c1 = new MyClass();
var c2 = new MyClass();

// declare 2 labels to instances on the stack
var s1 = new MyStruct();
var s2 = new MyStruct();

c1 = c2; // copies the reference data which is the pointer internally, c1 and c2 both point to the same instance
s1 = s2; // copies the data which is the struct internally, c1 and c2 both point to their own instance with the same data
于 2012-04-06T03:41:12.150 に答える