I think you are confusing classes with where the memory lives with how the memory is held on to. When you create an instance of a normal class, the memory of that instance lives on the heap. A reference to this instance might be in an object on the heap (if you set a member variable inside a different instance of an object to it); or a stack variable (if you declared a variable to the object inside a method or passed it to a function call), or it may be in the list of global roots (if it is a static reference, for example a Singleton reference).
A static class cannot be instantiated. There is no "reference" to it anywhere. Its methods are just functions loaded into memory when the CLR loads the assembly. You could create a delegate that points to one of these methods, but that doesn't make a reference to an instance the class, either. That's just a pointer to a function.
For example, look at this code:
class ObjectWrapper
{
Object obj = new Object();
}
static void Main(string[] args)
{
ObjectWrapper wrapper = new ObjectWrapper();
...
}
The Main method creates an instance of an ObjectWrapper class. This instance lives on the heap.
Inside the ObjectWrapper instance, there is an instance of class Object that lives on the heap. The reference to this class is inside the instance, so I guess you could think of the reference as "living in the heap".
Now, compare this to the following code:
class Singleton
{
static readonly instance = new Singleton();
}
The instance of the Singleton object lives on the heap, too. However, the reference is a static reference. It is maintained by the CLR in a list of global or "root" references.
Now look at this static class:
class ObjectWrapper
{
Object obj = new Object();
}
static class HelperMethods
{
static int DoSomethingUseful(ObjectWrapper wrapper1)
{
ObjectWraper wrapper2 = wrapper1;
// code here
}
}
HelperMethods is a static class. You cannot instantiate the HelperMethods class. This class will never consume memory on the heap. However, in the DoSomethingUseful method, it has two references to the ObjectWrapper on the stack. One is passed in, and one is created.