1

In the code below, when I examine the Chars variable while stepping through the code in the debugger, the size of the char array is 0 before the return line in the last iteration, but after the return line its 1 and continues growing back to the original size.

Why is this happening? thanks for any help in advance.

static void Main(string[] args)
{
    string str = "Hello";
    PrintReverse(str.ToArray()); // prints "olleH"
    Console.Read();
}

static void PrintReverse(char[] Chars)
{
    Console.Write(Chars[Chars.Length - 1]);
    Array.Resize(ref Chars, Chars.Length - 1);
    if (Chars.Length == 0) return;
    PrintReverse(Chars);
}
2
  • 1
    Which char array? Your question is unclear. Commented Jun 13, 2012 at 15:34
  • I tested this out and it simply printed the string in reverse. I had to add the System.Linq namespace to my project, because you are calling str.ToArray() instead of str.ToCharArray(). Commented Jun 13, 2012 at 15:38

3 Answers 3

2

You have two issues here.

First, the 'before/after return' issue means you are seeing two different execution frames-- that is, in the debugger the stack trace will show a bunch of PrintReverses on top of each other because each is there in its own context, with its own state, concurrently. It's almost (though not really) like an 'instance' of that method, and you're seeing two different ones.

Second, because each has its own state, the local variables in each-- including, critically, the parameters-- are also duplicated. They initially point to the same heap object (your initial Char array), but they are all different variables.

Now, look at this code:

char[] test1 = new char[] { '1', '2', '3' }, test2 = test1;
Array.Resize(ref test2, 2);
MessageBox.Show(new string(test1) + " - " + new string(test2)); // result: 123 - 12

If you run this, you'll see that although the variables initially refer to the same object, Array.Resize creates a new object and changes the reference of the variable passed in to point to the new one. The reference in the first variable remains pointing at the old (immutable) object.

This is what's happening in your case, only with the Chars parameter. In each method, you reassign Chars to point elsewhere using Array.Resize(), but the original variables remain referencing the old location.

Sign up to request clarification or add additional context in comments.

Comments

2

Try adding ref to the parameter declaration, this should now work the way you expected it to.

Without ref the call to Array.Resize can only modify the local array reference and not the reference passed in from Main.

    static void Main(string[] args)
    {
        string str = "Hello";
        var array = str.ToArray();
        PrintReverse(ref array);
        Console.Read();
        Debug.Assert(array.Length == 0);
    }
    static void PrintReverse(ref char[] Chars)
    {
        Console.Write(Chars[Chars.Length - 1]);
        Array.Resize(ref Chars, Chars.Length - 1);
        if (Chars.Length == 0) return;
        PrintReverse(ref Chars);
    }

Edit:

I was mistaken about ref causing a shallow clone, this is the proof:

    static void Main(string[] args)
    {
        var array = new[] { new object() };
        TestRef(ref array, array);
    }

    static void TestRef(ref object[] arrayByRef, object[] arrayByValue)
    {
        Debug.Assert(ReferenceEquals(arrayByRef, arrayByValue)); //no difference whether passed by ref or value, if there was a shallow clone happening, this would fail
        Array.Resize(ref arrayByRef, 2);
        Debug.Assert(!ReferenceEquals(arrayByRef, arrayByValue)); //only now do they differ
    }

6 Comments

He was asking why the array was growing in size in the debugger, AFAIK the program is working as he intended
@KDiTraglia It appears to grow from 0 to 5 as the stack unwinds, but actually he is looking at 6 different arrays. My solution explains how he could avoid this if he needs to and also trys to explain why it is happening.
Yeah that's a good point, I'll edit my answer since it got the check mark
"Without ref it will work on a new shallow clone" - can you elaborate this a bit?
@Groo - No I can't, because I'm wrong! See my updated answer.
|
1

Think about the chain of execution. You are recursively calling the method that shrinks the array until you get to 0, then you return to the caller (the same method) so you are seeing the grow back to the original size as you traverse back up the call stack.

No extra logic is happening as a result of this, as the recursive call is the last call in the method, but you get to see the debugger end each call, which in turn had an array size 1 bigger than the call before it.

If you were to pass the array by reference instead, however, the array size would remain at size 0 as it came up the call stack because each successive call to Array.Resize would create a new array and update all of the references to the new array instead of only the reference local to that call. (where as if you don't pass by reference it updates only a copy of the reference, and does not update those in the calls before it).

This is because Array.Resize creates a new array and updates the reference to point to the new array instead of the old array, and by not passing by reference, you are sending a copy of the reference to the original array instead of the actual reference to the array, thus the calls to Array.Resize do not update the old references.

static void PrintReverse(ref char[] Chars)
{
    Console.Write(Chars[Chars.Length - 1]);
    Array.Resize(ref Chars, Chars.Length - 1);
    if (Chars.Length == 0) return;
    PrintReverse(ref Chars);
}

Thanks Groo for correcting me, hopefully I have it right this time

3 Comments

But the reason to this is that, internally, Array.Resize always creates a new array instance, then copies elements, and then replaces the reference. This is why previous array instances are left on the stack. If Array.Resize operated differently and mutated the original array, it would remain changed.
Your comment is not correct. First, Array.Resize is neither "mutating elements", nor mutating the array itself. It creates a new instance in memory, copies elements, and then switches the reference so that Chars is pointing to the new instance. Old array is not collected only because the previous stack frame still holds its reference. Second, passing an array by value doesn't "create a new instance" of the array, it only creates a copy of the reference to the array. When a method is entered, no copying is being done.
@Groo You are correct, did some testing on this, quite a complex little problem. Hopefully my answer is correct now, thanks for correcting me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.