简体   繁体   中英

Recursion, C# with array of char after return

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);
}

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
    }

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 PrintReverse s 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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM