10

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

dijxtra
  • 2,681
  • 4
  • 25
  • 37
  • By the way, it is logical you get the same output result (However I understand you are waiting for the result `success` to be printed) because both `Console.WriteLine` lines are executed *after* the `Init()` method. – Otiel Oct 21 '11 at 18:02

6 Answers6

14

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.

dlev
  • 48,024
  • 5
  • 125
  • 132
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
3

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);
    }
}
Steve
  • 31,144
  • 19
  • 99
  • 122
  • This solution is most elegant of those proposed here, thank you :-) – dijxtra Oct 21 '11 at 21:06
  • Informative Q&A, but I have to disagree, using `StringBuilder` in this way breaks its semantic purpose and is possibly more expensive. It's possibly the shortest path, LOC-wise, but goes "against the grain" of the CLR. Eric's simple `MyRef` communicates purpose more cleanly and has the additional benefit of working for any given type. Very nice. – Marc L. Oct 28 '11 at 13:56
  • @MarcL. - Yeah, I'd agree with that. My approach was definitely a quick and dirty one based mostly on what I perceived to be the (negative) reaction to the question when initially posted. – Steve Oct 28 '11 at 16:27
2

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();
    }
Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
2

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.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
1

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.

IAbstract
  • 19,551
  • 15
  • 98
  • 146
1

As answered by JaredPar, you can't.

But the problem is partly that string is immutable. Change your parameter to be of class Basket { public string status; } and your code would basically work. No need for the ref keyword, just change parameter.status.

And the other option is of course Console.WriteLine("After: {0}", e.parameter);. Do wrap parameter in a (write-only) property.

H H
  • 263,252
  • 30
  • 330
  • 514
  • I generally dislike the suggestion it has anything to do with the immutablity of string, which is quite often present for questions involving string assignments. Reassignment is not a mutation, mutable type or not. Implying it has anything to do with immutability is a distortion, wouldn't you agree? (Although the suggestion of encapsulating the value in a class is valid.) – Anthony Pegram Oct 21 '11 at 18:02
  • 2
    Not this old dog again.... Its only is a reassignment *because* strings are immutable. – Steve Oct 21 '11 at 18:06
  • @Steve, to be clear, you must *use* reassignment *because* you can't mutate the original string. But my larger point is that assignment is assignment, the type itself could be mutable or immutable and the assignment behavior `x = y;` would be the same. The larger larger point is that my reading of the question is that the type itself was irrelevant, he wanted to maintain a reference or alias to the original variable passed. To that end, all answers confirm it isn't directly possible but have offered ways to work within the type system to achieve the overall goal. – Anthony Pegram Oct 21 '11 at 18:49
  • (Bah, sorry for being on a pedantic soapbox, +1s all around.) – Anthony Pegram Oct 21 '11 at 18:49
  • As an example to illustrate Anthony's point, consider a field of type List rather than of type string. Lists are mutable. Still, `obj.StringList = new List();` is an assignment, not a mutation. – phoog Oct 21 '11 at 18:58