I've never read the C# spec, but I've been using the language since well before it was released in 2002.
The stack and heap may be abstract, but the .NET implementations of C# include
The managed memory heap. Allocations (using new
) of reference types result in memory being allocated from this heap (conceptually there is a single heap, the normal implementation uses more than one). This heap is managed by the Garbage Collector - references into this heap are tracked, and when an object allocated on this heap no longer has any references, it becomes eligible for collection. How this all works is an implementation detail, but the concept is required.
Function calls require a place to store the return address and the call parameters. Not all machine architecture store these in a stack (though I think every architecture I've seen includes a stack into which excess call parameters (more than can be passed in registers) will spill over into).
Local variables (value type variables and references to instances of reference types) need a place to be stored. Their scope is bound to the lifetime of the function call. They tend to be stored in a stack as well.
It's worth noting that not all local value type variables are now stack-resident. With the advent of variables captured in a closure and of async
functions, the compiler can do magic tricks; storing seemingly local value type variables in instances of hidden classes.