First, note that all of this is an implementation detail. The only thing the runtime guarantees is:
- When you ask for a static field, it's there
- A static constructor is executed at some point before you use the type
That's pretty much it. Everything else is an implementation detail - the specification doesn't care about stack, heap or anything else. It's up to the implementation of the runtime, and a valid runtime could put everything on the stack, if it so desired, or on the heap. And don't forget registers.
Now, let's see some of the misconceptions you already managed to pick up:
- Static is just for the lifetime - yes. It doesn't say anything about when or where it is stored - just that it's available when you asked for it. A compliant runtime is free to use whatever memory it wants, or even never to load the fields in memory (e.g. keeping it in the image, which is already in memory anyway)
- Static will go on heap, for life time - most likely, yes. But it's not part of the specification, and a compliant runtime can store it wherever it wants, or nowhere at all, as long as the proper guarantees hold. Also, don't forget that "for life time" means "at least for the life time of the AppDomain"; it may or may not be released when the domain is unloaded.
- Static value type will go on stack, for life time - most likely, no. Again, an implementation detail, but the stack has completely different semantics than what makes sense for a static value. And the next point will give you more of a reason why:
- When the assambly is loaded, all static constructors are called, including static fields. - No. There is no such requirement, and no such guarantee. If you rely on this, your program is going to break (and I've seen that plenty of times before). Again, an implementation detail, but in the current MSCLR implementations, statics tend to be allocated in a heap of their own, and some time before the type they are defined in is needed. You can easily see this if you throw an exception in a static constructor - it will cause a
TypeLoadException
, most likely in a method that first references the type (needless to say, this can make debugging statics tricky).
- Reference types go on the heap, value types go on the stack. - No. This is confusing the mechanism with the semantics. The only difference between the two are their semantics - everything else is up to the implementation. If a runtime can preserve reference semantics for reference types on the stack, that's perfectly valid. And even with current MSCLR runtimes, value types are stored on the heap all the time - whenever they are boxed, or members of a reference type, for example.
Some folks may be confused. Some don't understand the difference between the contract, and the practical implementations. Some simply don't know what they're talking about. I wish there was an easy way to know which is which, but there isn't. If in doubt, you can go to the C#/CLR specifications, but that only tells you about the contract, not the practical reality.
The whole point of managed memory is that you aren't supposed to care about these implementation details. Of course, like any abstraction, it leaks - and it makes sense to know how things really are, down to the CPU micro-intructions, memory caching etc., through all the various layers and abstractions. But it's nothing to rely on - the implementation can change at any time, and it has many times in the past.