web-dev-qa-db-ja.com

「参照」によってc#のクラスフィールドに割り当てるにはどうすればよいですか?

C#でクラスフィールドに「参照」して割り当てる方法を理解しようとしています。

次の例を検討します。

 public class X
 {

  public X()
  {

   string example = "X";

   new Y( ref example );

   new Z( ref example );

   System.Diagnostics.Debug.WriteLine( example );

  }

 }

 public class Y
 {

  public Y( ref string example )
  {
   example += " (Updated By Y)";
  }

 }

 public class Z
 {

  private string _Example;

  public Z( ref string example )
  {

   this._Example = example;

   this._Example += " (Updated By Z)";

  }

 }

 var x = new X();

上記のコードを実行すると、出力は次のようになります。

X (Updated By Y)

そしてない:

X (Updated By Y) (Updated By Z)

私が望んだように。

「参照パラメーター」をフィールドに割り当てると、参照が失われるようです。

フィールドに割り当てるときに参照を保持する方法はありますか?

ありがとう。

28
Jamie

いいえ。refは純粋に呼び出し規約です。フィールドの修飾には使用できません。 Zでは、_Exampleは渡された文字列参照の値に設定されます。次に、+ =を使用して新しい文字列参照をそれに割り当てます。例に割り当てることはないため、参照は効果がありません。

必要な唯一の回避策は、参照(ここでは文字列)を含む共有の可変ラッパーオブジェクト(配列または架空のStringWrapper)を用意することです。一般に、これが必要な場合は、クラスが共有する大きな可変オブジェクトを見つけることができます。

 public class StringWrapper
 {
   public string s;
   public StringWrapper(string s)
   {
     this.s = s;
   }

   public string ToString()
   {
     return s;
   }
 }

 public class X
 {
  public X()
  {
   StringWrapper example = new StringWrapper("X");
   new Z(example)
   System.Diagnostics.Debug.WriteLine( example );
  }
 }

 public class Z
 {
  private StringWrapper _Example;
  public Z( StringWrapper example )
  {
   this._Example = example;
   this._Example.s += " (Updated By Z)";
  }
 }
9

他の人が指摘したように、「変数への参照」タイプのフィールドを持つことはできません。ただし、それができないことを知っているだけでは、おそらく満足できません。あなたはおそらく最初に、なぜそうでないのか、そして次に、この制限を回避する方法を知りたいでしょう。

その理由は、3つの可能性しかないためです。

1)refタイプのフィールドを許可しない

2)refタイプの安全でないフィールドを許可する

3)ローカル変数(別名「スタック」)に一時ストレージプールを使用しない

Ref型のフィールドを許可したとします。その後、あなたはできる

public ref int x;
void M()
{
    int y = 123;
    this.x = ref y;
}

そして、Mの完了後にyにアクセスできるようになりました。これは、(2)の場合、つまりthis.xにアクセスすると、yのストレージが存在しないためにクラッシュして恐ろしく死ぬか、または(3)の場合と、ローカルyは、一時メモリプールではなく、ガベージコレクションされたヒープに格納されます。

ローカル変数がrefによって渡されている場合でも一時プールに格納されるという最適化が気に入っています。時限爆弾を放置するとプログラムがクラッシュして後で死ぬ可能性があるという考えは嫌いです。したがって、オプション1は、参照フィールドなしです。

無名関数の閉じた変数であるローカル変数の場合、オプション(3)を選択することに注意してください。これらのローカル変数は一時プールから割り当てられません。

次に、2番目の質問が表示されます。どうすればそれを回避できますか? refフィールドが必要な理由が別の変数のゲッターとセッターを作成することである場合、それは完全に正当です。

sealed class Ref<T>
{
    private readonly Func<T> getter;
    private readonly Action<T> setter;
    public Ref(Func<T> getter, Action<T> setter)
    {
        this.getter = getter;
        this.setter = setter;
    }
    public T Value { get { return getter(); } set { setter(value); } }
}
...
Ref<int> x;
void M()
{
    int y = 123;
    x = new Ref<int>(()=>y, z=>{y=z;});
    x.Value = 456;
    Console.WriteLine(y); // 456 -- setting x.Value changes y.
}

そして、あなたは行き​​ます。 yはgcヒープに格納され、xyを取得および設定する機能を持つオブジェクトです。

CLRはref localsおよびref returnメソッドをサポートしていますが、C#はサポートしていないことに注意してください。おそらく、C#の仮想的な将来のバージョンでこれらの機能がサポートされる予定です。私はそれをプロトタイプ化しました、そしてそれはうまくいきます。しかし、これは優先度の高いリストではそれほど高くないので、期待はしません。

更新:上記の段落で述べた機能は、最終的にC#7で実際に実装されました。ただし、フィールドに参照を保存することはできません。

68
Eric Lippert

Zクラスの参照を更新するのを忘れました:

public class Z {
    private string _Example;

    public Z(ref string example) {
        example = this._Example += " (Updated By Z)";
    }
}

出力:X(Yによる更新)(Zによる更新)

文字列の+ =演算子はString.Concat()メソッドを呼び出すことに注意してください。 new文字列オブジェクトを作成しますが、文字列の値は更新しません。文字列オブジェクトは不変であり、文字列クラスには値を変更できるメソッドやフィールドがありません。通常の参照タイプのデフォルトの動作とは大きく異なります。

したがって、文字列メソッドまたは演算子を使用する場合は、常に戻り値を変数に代入する必要があります。これはかなり自然な構文で、値の型は同じように動作します。文字列の代わりにintを使用した場合、コードは非常に似ています。

3
Hans Passant