Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting a ref to a member field in C#

I'd like to assign a reference to a member field. But I obviously do not understand this part of C# very well, because I failed :-) So, here's my code:

public class End {
    public string parameter;

    public End(ref string parameter) {
        this.parameter = parameter;
        this.Init();
        Console.WriteLine("Inside: {0}", parameter);
    }

    public void Init() {
        this.parameter = "success";
    }
}

class MainClass {
    public static void Main(string[] args) {
            string s = "failed";
        End e = new End(ref s);
        Console.WriteLine("After: {0}", s);
    }
}

Output is:

Inside: failed
After: failed

How do I get "success" on the console?

Thanks in advance, dijxtra

like image 785
dijxtra Avatar asked Oct 21 '11 17:10

dijxtra


5 Answers

As others have pointed out, you cannot store a reference to a variable in a field in C#, or indeed, any CLR language.

Of course you can capture a reference to a class instance that contains a variable easily enough:

sealed class MyRef<T>
{
  public T Value { get; set; }
}
public class End 
{
  public MyRef<string> parameter;
  public End(MyRef<string> parameter) 
  {
    this.parameter = parameter;
    this.Init();
    Console.WriteLine("Inside: {0}", parameter.Value);
  }
  public void Init() 
  {
    this.parameter.Value = "success";
  }
}
class MainClass 
{
  public static void Main() 
  {
    MyRef<string> s = new MyRef<string>();
    s.Value = "failed";
    End e = new End(s);
    Console.WriteLine("After: {0}", s.Value);
  }
}

Easy peasy.

like image 113
Eric Lippert Avatar answered Nov 06 '22 19:11

Eric Lippert


There are really two issues here.

One, as the other posters have said, you can't strictly do what you're looking to do (as you may be able to with C and the like). However - the behavior and intent are still readily workable in C# - you just have to do it the C# way.

The other issue is your unfortunate attempt to try and use strings - which are, as one of the other posters mentioned - immutable - and by definition get copied around.

So, having said that, your code can easily be converted to this, which I think does do what you want:

public class End
{
    public StringBuilder parameter;

    public End(StringBuilder parameter)
    {
        this.parameter = parameter;
        this.Init();
        Console.WriteLine("Inside: {0}", parameter);
    }

    public void Init()
    {
        this.parameter.Clear();
        this.parameter.Append("success");
    }
}

class MainClass
{
    public static void Main(string[] args)
    {
        StringBuilder s = new StringBuilder("failed");
        End e = new End(s);
        Console.WriteLine("After: {0}", s);
    }
}
like image 31
Steve Avatar answered Nov 06 '22 19:11

Steve


It sounds like what you're trying to do here is make a field a reference to another storage location. Essentially having a ref field in the same way you have a ref parameter. This is not possible in C#.

One of the main issues with doing this is that in order to be verifiable the CLR (and C#) must be able to prove the object containing the field won't live longer than the location it points to. This is typically impossible as objects live on the heap and a ref can easily point into the stack. These two places have very different lifetime semantics (heap typically being longer than the stack) and hence a ref between can't be proven to be valid.

like image 34
JaredPar Avatar answered Nov 06 '22 20:11

JaredPar


If you don't want to introduce another class like MyRef or StringBuilder because your string is already a property in an existing class you can use a Func and Action to achieve the result you are looking for.

public class End {
    private readonly Func<string> getter;
    private readonly Action<string> setter;

    public End(Func<string> getter, Action<string> setter) {
        this.getter = getter;
        this.setter = setter;
        this.Init();
        Console.WriteLine("Inside: {0}", getter());
    }

    public void Init() {
        setter("success");
    }
}

class MainClass 
{
    public static void Main(string[] args) 
    {
        string s = "failed";
        End e = new End(() => s, (x) => {s = x; });
        Console.WriteLine("After: {0}", s);
    }
}

And if you want to simplify the calling side further (at the expense of some run-time) you can use a method like the one below to turn (some) getters into setters.

    /// <summary>
    /// Convert a lambda expression for a getter into a setter
    /// </summary>
    public static Action<T, U> GetSetter<T,U>(Expression<Func<T, U>> expression)
    {
        var memberExpression = (MemberExpression)expression.Body;
        var property = (PropertyInfo)memberExpression.Member;
        var setMethod = property.GetSetMethod();

        var parameterT = Expression.Parameter(typeof(T), "x");
        var parameterU = Expression.Parameter(typeof(U), "y");

        var newExpression =
            Expression.Lambda<Action<T, U>>(
                Expression.Call(parameterT, setMethod, parameterU),
                parameterT,
                parameterU
            );

        return newExpression.Compile();
    }
like image 23
Ian Mercer Avatar answered Nov 06 '22 21:11

Ian Mercer


At this line:

this.parameter = parameter;

...you copy the method parameter to the class member parameter. Then, in Init() you are assigning the value "success", again, to the class member parameter. In your Console.Writeline, then, you are writing the value of the method parameter, "failed", because you never actually modify the method parameter.

What you are trying to do - the way you are trying to do it - is not possible, I believe in C#. I wouldn't try passing a string with the ref modifier.

like image 1
IAbstract Avatar answered Nov 06 '22 19:11

IAbstract