开发者

How do I assign by "reference" to a class field in C#?

I am trying to understand how to assign by "reference" to a class field in C#.

I have the following example to consider:

 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;

        开发者_Go百科 this._Example += " (Updated By Z)";
     }
 }

 var x = new X();

When running the above code the output is:

X (Updated By Y)

And not:

X (Updated By Y) (Updated By Z)

As I had hoped.

It seems that assigning a "ref parameter" to a field loses the reference.

Is there a way to keep hold of the reference when assigning to a field?


As others have noted, you cannot have a field of "ref to variable" type. However, just knowing that you cannot do it is probably unsatisfying; you probably also want to know first, why not, and second, how to get around this restriction.

The reason why is because there are only three possibilities:

1) Disallow fields of ref type

2) Allow unsafe fields of ref type

3) Do not use the temporary storage pool for local variables (aka "the stack")

Suppose we allowed fields of ref type. Then you could do

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

and now y can be accessed after M completes. This means that either we're in case (2) -- accessing this.x will crash and die horribly because the storage for y no longer exists -- or we're in case (3), and the local y is stored on the garbage collected heap, not the temporary memory pool.

We like the optimization that local variables be stored on the temporary pool even if they are being passed by ref, and we hate the idea that you could leave a time bomb around that could make your program crash and die later. Therefore, option one it is: no ref fields.

Note that for local variables that are closed-over variables of anonymous functions we choose option (3); those local variables are not allocated out of the temporary pool.

Which then brings us to the second question: how do you get around it? If the reason you want a ref field is to make a getter and setter of another variable, that's perfectly legal:

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.
}

And there you go. y is stored on the gc heap, and x is an object that has the ability to get and set y.

Note that the CLR does support ref locals and ref returning methods, though C# does not. Perhaps a hypothetical future version of C# will support these features; I have prototyped it and it works well. However, this is not real high on the priority list, so I wouldn't get my hopes up.

UPDATE: The feature mentioned in the paragraph above was finally implemented for real in C# 7. However, you still cannot store a ref in a field.


No. ref is purely a calling convention. You can't use it to qualify a field. In Z, _Example gets set to the value of the string reference passed in. You then assign a new string reference to it using +=. You never assign to example, so the ref has no effect.

The only work-around for what you want is to have a shared mutable wrapper object (an array or a hypothetical StringWrapper) that contains the reference (a string here). Generally, if you need this, you can find a larger mutable object for the classes to share.

 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)";
  }
 }


You forgot to update the reference in the Z class:

public class Z {
    private string _Example;

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

Output: X (Updated By Y) (Updated By Z)

Point to keep in mind is that the += operator for a string calls the String.Concat() method. Which creates a new string object, it doesn't update the value of a string. String objects are immutable, the string class doesn't have any methods or fields that lets you change the value. Very different from the default behavior of a regular reference type.

So if you use a string method or operator, you always have to assign the return value back to a variable. This is pretty natural syntax, value types behave the same way. Your code would be very similar if you used an int instead of a string.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜