tags:

views:

289

answers:

2

I was wondering how one could store a reference to an object in .net.

That is, I would like something like the following code (note, of course, that the following code may be way off from how to actually do it):

class Test
{
    private /*reference to*/ Object a;
    public Test(ref int a)
    {
        this.a = a;
        this.a = ((int)this.a) + 1;
    }
    public Object getA() { return this.a; }
}
/*
 * ...
 */
static void Main(string[] args)
{
    int a;
    a=3;
    Test t = new Test(ref a);
    Console.WriteLine(a);
    Console.WriteLine(t.getA());
    Console.ReadKey();
}

To produce the following output:

4
4

Ideally, I would like to do this without writing a wrapper class around the integer.

In other words, I think I want pointers in .Net.

+23  A: 

You cannot store references to variables in .NET, period. You can store references to objects, but not references to variables.

The reason is that if you were allowed to store references to arbitrary variables then you could store references to local variables. If you can store references to local variables then the runtime cannot use the optimization of storing local variables on the short-lived memory pool, aka, the stack.

Now, even if you could do that, the operation you are describing is not typesafe for a different reason. You have a (very badly named) field variable "a" of type "reference to object variable" and a (very badly and confusingly named) local variable "a" of type "reference to int variable". Even if you could store a reference to a variable it doesn't make any sense to store a reference to an int variable in something of type "reference to object variable" because those two types are logically not compatible. The operations you can perform on them are different; a reference to an object variable can have a string written into it; a reference to an int variable cannot.

Perhaps I am misunderstanding but wouldn't a variable such as the integer above be boxed into an object which could then be stored as a reference?

You are confusing references to objects with references to variables. It is confusing that we use the same terminology for what is really two different things.

Yes, boxing turns a value type, like int, into a reference type, like object. That has ABSOLUTELY NOTHING WHATSOEVER to do with references to variables.

When you make a ref to a variable you are making an alias for that variable. When you say

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

you are saying "x and y are two different names for the same variable".

Now, if what you want to do is represent the notion of "I have captured a variable and I want to be able to read and write it" then use delegates:

class Ref<T>
{
    private Func<T> getter;
    private 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); }
    }
}
...
int abc = 123;
var refabc = new Ref<int>(()=>abc, x=>{abc=x;});
... now you can pass around refabc, store it in a field, and so on
refabc.Value = 456;
Console.WriteLine(abc); // 456
Console.WriteLine(refabc.Value); // 456

Make sense?

Eric Lippert
Perhaps I am misunderstanding but wouldn't a variable such as the integer above be boxed into an object which could then be stored as a reference?
Jack
Closures capture local-variable references.
Marcelo Cantos
It does. Thanks. I guess I'm just too used to C.
Jack
This is probably a topic for a separate question, but doesn't capturing variables involve boxing? I have to do some research I guess to find out how it works, but I have a strange gut feeling that it's the case. Otherwise the stack would have to be "locked" to prevent unwinding. And if I'm right isn't explicit boxing simpler?
Maciej Hehl
@Maciej: Why would it involve boxing? Boxing is the process of converting a *value* to an *object*. Why do you think that has anything to do with *building a closure*? And what do you think the stack has to do with it? Closed-over local variables aren't stored on the stack in the first place.
Eric Lippert
good point with the delegates (though any one trying to sneak that under the review radar at my place would need a very good argument for using it :) )
Rune FS
@Eric Lippert Ok, thank You. Well apparently I misused the term boxing (I stand corrected). All I know is what I understood form the C# programming guide form MSDN Library (which I'm still reading) and it looks like I memorised the process of wrapping the value in a reference type and storing it on the managed heap, and didn't pay much attention to the formal conversion to an object type (or an interface). What I meant basically, was that normally (without capturing) the variable abc would be allocated on the stack. If it gets captured, it can't be allocated on the stack any more.
Maciej Hehl
Well I don't have a proof and I din't read about it (yet), but that's what I thought - You can call it intuition. So in my mental model when the compiler determines that the variable is captured, it has to store it on the heap which I erroneusly called boxing. I'm just that type of a guy, who doesn't like black magic going behind the scenes and has to get used to it, so my first impulse was to propose avoiding it and doing explicitly what the compiler has to do anyway, but after a bit of thought I changed my mind :).
Maciej Hehl
@Maciej: When you say, for example, "class C { int x; }", the *storage associated with x* is on the heap. And when you have a normal local variable "object x = 123;" the *reference in the variable* is on the heap, but the *storage location of x* is on the stack. Does that explain it? You are confusing the location of a *value* with the location of *storage*. You can have a *variable* which is storage on the stack, you can have a *variable* which is storage on the heap, and you can have *values* on the stack or the heap. Boxing puts a *value* on the heap; hoisting puts a *variable* on the heap.
Eric Lippert
But really you shouldn't need to worry about any of this. The compiler and the runtime figure out where data can most efficiently be stored given how it will be used. There is all kinds of crazy black magic happening behind the scenes all the time; don't worry about the magic, worry about your program semantics.
Eric Lippert
@Eric Lippert Thank You very much. I appreciate the effort. I understand (I think) what is stored where, but somehow I tend to name things backwards :). I would say that in case of object x = 123; x is a reference. It is stored on the stack (the address) and references a value (123) wrapped up in some object and stored on the heap. Maybe it is because I tend to think a bit like in C++ and try to fit things into this mental model, which might be incompatible. Oh well maybe one day :)
Maciej Hehl
@Jack, @Eric: Actually my response was not to Jack's comment, but to Eric's opening sentence (apologies for the confusion). Lambdas store references to variables and can even induce such variables to out-live their lexical scope. I suppose, strictly speaking, you can call it "capturing a variable" instead of "referencing a variable", but a rose by any other name...
Marcelo Cantos
@Marcelo: lambdas do not "store references to variables". There is no reference to a variable. Lambdas *extend the lifetimes* of their outer variables, but they do not take *references* to those outer variables. The only time you take a reference to a variable in C# is (1) with the ref modifier, (2) with the out modifier, or (3) when passing "this" to a method of a value type.
Eric Lippert
@Eric: what is the difference, semantically (implementation differences don't interest me), between extending the lifetime of a variable and taking a reference to it? Both forms grant some code read and/or write access to the variable's "cell". (Given that you are on the C# team, I shall assume, _a priori_, that you are right and I am wrong, but I am struggling to see why.)
Marcelo Cantos
@Marcelo: But you are asking a question about an implementation detail, so it is impossible to answer the question without referring to implementation details. Look at it this way: a variable is defined as a *storage location*. Therefore a "reference to a variable" must be a reference to a storage location. When you pass a variable to a method taking a "ref" parameter what is actually passed is a reference to a variable -- the managed address of a storage location. That's an implementation detail.
Eric Lippert
@Marcelo: When you hoist an outer variable of a lambda, we generate code that tells the runtime "make sure that the storage associated with this local is on the heap, not on the stack". Nowhere in there did we generate any code that takes the managed address of the local. There are two concepts here: (1) generate code to take the managed address of a storage location to make a reference to a variable, and (2) generate code to ensure that a particular storage is on the long-lived heap, not the short-lived pool. Those are two completely different things that have nothing to do with each other.
Eric Lippert
@Eric: thank you for the clear answer, but the sense I get is, "The difference is only in the implementation, therefore, you are asking about an implementation detail". As a user without insight into the plumbing, I care more about what things _mean_, not how they are coded internally. In this instance, I see no meaningful distinction between passing a ref parameter to a function vs. passing a `setter`/`getter` pair; the purpose and outcome are identical. I get that references to variables can't be held, but what I'm saying is that lambdas behave, for all intents and purposes, as if they can.
Marcelo Cantos
+3  A: 

C# has no concept of a reference variable akin to C++'s int& a. There are workarounds. One is to use closures:

class Test
{
    private Func<int> get_a;
    private Action<int> set_a;
    public Test(Func<int> get_a, Action<int> set_a)
    {
        this.get_a = get_a;
        this.set_a = set_a;
        this.set_a(this.get_a() + 1);
    }
    public Object getA() { return this.get_a(); }
}
/*
 * ...
 */
static void Main(string[] args)
{
    int a;
    a=3;
    Test t = new Test(() => a, n => { a = n; });
    Console.WriteLine(a);
    Console.WriteLine(t.getA());
    Console.ReadKey();
}

I'm not in front of VS, so please excuse any embarrassing faux pas.

Marcelo Cantos
+1, not something i'd use in production code; but cute nonetheless
Pierreten
Apparently great minds think alike. :-)
Eric Lippert