views:

91

answers:

5

Note the following code:

Control foo = null;
Control bar = null;
int i = 0;

protected void Page_Load(object sender, EventArgs e)
{
    test();
    test();
    Page.Controls.Add(foo);
}

void test()
{
    i++;
    bar = new Control();
    bar.Controls.Add(new LiteralControl(i.ToString()));
    if (foo == null)
    {
        foo = new Control();
        foo.Controls.Add(bar);
    }
}

When trying out the above code, I was suprised to see the result printed is '1' (not '2').

Im assuming this is because when i'm adding the control bar to foo, foo.Controls.Add() resolves the reference bar, rather than just storing the reference itself.

1) Can anyone confirm this is the case, or possibly elaborate?

2) I have a feeling if I was allowed to do foo.Controls.Add(ref bar); it would show '2', but obviously that syntax is illegal. Is it possible for this to be the case without major refactoring?

+3  A: 

It's a lot simpler than that. foo is an object of class scope. The second time you run test(), nothing happens to it because it's not null. So it's just returning what you added to it from the first test(), which was a new LiteralControl with the string value of "1". The "1" is just text, it's not a reference to i.

By the way foo.Controls.Add(ref bar) is actually what happens. Controls (objects) are always added by reference, that is the nature of their existence. To pass the actual values of an object to something, you would basically have to make a copy of the object first. But you'd still just be passing a reference to the copy.

If you changed the contents of bar, it would indeed change the output.

I think what you are confused about here is that you aren't dealing with the same bar the second time you run test(), since you create a new one each time.

(edit)

By the way if you changed just this line:

 bar = new Control();

to

if (bar == null) bar = new Control();

it would behave as expected, because the 2nd time through you would not throw out the original "bar" reference by replacing it with a new one as you are now. (Sorta... acutally it would then say "12" because you'd have added TWO LiteralControls to it).

jamietre
+6  A: 

The call to

foo.Controls.Add(bar);

uses the current value of bar. That refers to a Control containing a LiteralControl with the text "1".

Now later on, you change the value of bar to refer to a completely different control... but that makes no difference to what's already stored in foo.Controls. Even if you could pass bar by reference into the Add method, that wouldn't actually make any difference.

I would strongly suggest that if you're not sure how references, parameters etc work in .NET that you use console applications to check your understanding. They're much simpler to play with, debug etc.

You might also want to read my articles on parameter passing and value/reference types.

Jon Skeet
A: 

The first time test() is run a new control (I'll call it Control1) is created and a reference to it stored in bar. because foo is null, it gets set to a new control, to which the reference in bar is added.

So now Control1 is now referenced by both bar, and the foo.Controls collection.

The second time test() is run a new control is created (Control2), an and a reference stored in bar. foo is now no null, so is not modified.

So now bar is a reference to Control2, but foo.Controls still contains a reference to Control1.

Andrew Cooper
A: 

Well foo only gets created once (on the first call to test) and then adds the LiteralControl with a label of 1.

On the second call to test(), the old reference (label 1) is replaced with a new control. However, the bar that was added to foo still exists and is unaffected.

orvado
+3  A: 

Jon Skeet's answer is good (of course), but I just want to emphasize one point:

Variables of reference type (such as bar and foo) don't contain objects, they contain references to objects.

So the first time through the test() method, bar contains a reference to a Control which has a 1 in it. And that reference gets added to foo.

The second time through the test() method, bar contains a reference to a different Control, which has a 2 in it. And that reference doesn't get added to foo.

Why is it a different reference the second time? Because at the start of test() you say bar = new Control(); That stores a reference to a new Control in bar. From there on, bar has nothing to do with the Control it previously referred to, although a reference to that control still lives on in foo's Controls collection.

If you want to change your code so it behaves how you expected, you could do this:

Control foo = null;
Control bar = new Control();
int i = 0;

protected void Page_Load(object sender, EventArgs e)
{
    test();
    test();
    Page.Controls.Add(foo);
}

void test()
{
    i++;
    bar.Controls.Clear()
    bar.Controls.Add(new LiteralControl(i.ToString()));
    if (foo == null)
    {
        foo = new Control();
        foo.Controls.Add(bar);
    }
}

In that case, we only store a new reference in bar one time, so when all is said and done it should still match the reference we added to foo.

Tim Goodman
+1 Exactly what I was trying to say, but far more eloquent.
Andrew Cooper