It’s more complicated than you might think. Even your claim that “value types are allocated on the stack” isn’t correct. For example:
class Foo
{
int x;
}
int
is a value type, but the value for x will always be on the heap because it will be stored with the rest of the data for the instance of Foo which is a class.
Additionally, captured variables for anonymous functions and iterator blocks make life trickier.
I have an article about C# heap/stack memory you may find useful, but you might also want to read Eric Lippert’s blog post on “The stack is an implementation detail”. In particular, a future C# compiler could decide to store all of its local variables on the heap, using the stack just to hold a reference to an instance created at the start of the method… that wouldn’t defy the C# spec at all.