Each time you call any method1 (the Main
method in your case), that creates a new stack frame - that takes up memory. You don't have an infinite amount of memory (particularly on the stack), so eventually you run out of stack space, at which point the exception is thrown.
Note that your method currently doesn't have any local variables or parameters, so each stack frame is relatively small... if you had a lot of local variables, it would throw the exception after fewer calls.
For example:
using System;
class Test
{
static void Main()
{
// Use whichever you want to demonstrate...
RecurseSmall(1);
//RecurseLarge(1);
}
static void RecurseSmall(int depth)
{
Console.WriteLine(depth);
RecurseSmall(depth + 1);
}
static void RecurseLarge(int depth)
{
Console.WriteLine(depth);
RecurseLarge(depth + 1);
// Create some local variables and try to avoid them being
// optimized away... We'll never actually reach this code, but
// the compiler and JIT compiler don't know that, so they still
// need to allocate stack space.
long x = 10L + depth;
long y = 20L + depth;
decimal dx = x * depth + y;
decimal dy = x + y * depth;
Console.WriteLine(dx + dy);
}
}
On my machine (with default compilation options), RecurseSmall
prints as far as depth 21175; RecurseLarge
prints as far as depth 4540.
The JIT compiler is also able to detect some cases where it can use what's called tail recursion to replace the existing stack frame with a new one, at which point the method call effectively doesn't require any more stack space. On my machine, if you compile the above code with:
csc /o+ /debug- /platform:x64
... it runs forever, and will never use much stack space. I generally consider it a bad idea to rely on this optimization though, as it can be hard to predict (and definitely depends on the exact JIT you're using).
Compare all of this with your while
loop, which uses whatever extra stack space is required by Console.WriteLine
, but that stack space is reclaimed after each call, so you never run out of space.
1 Logically, at least. Methods can sometimes be inlined by the JIT compiler, which will avoid this.