Class v. Struct
Memory usage and leaks in a managed world often come from unexpected places. Allocating classes or structs is a confusing subject to newcomers to Dot Net.
Multiple objects = new class
When you see many many variables that are grouped together in usage your first thought usually is, I should make this an object. Turning those variables into a class can often greatly simplify your programming. Now instead of having function that has lots of parameters, you can pass just the newly created class. But should that have been a struct?
Passing in classes vs structs
Often the line between should something be a class or a struct is very subtle, but the impact on your runtime memory usage can be quite large.
Once you start passing around a lot of classes you may see a large number of references to heap objects (where classes are stored). Many of these class instanced object may live for the entire lifetime of your application. This can often lead to people thinking that the GC has leaked the memory.
Creating a series of nested classes (classes that inherit or encapsulate another class) may lead to objects living longer than expected. Each object reference will prevent the object from being cleaned up. Many times this may have been an accidental reference, or one the programmer though was temporary, like passing complex objects into fuctions and using their internal variables.
The solution, structs.
By declaring an object as a struct instead of a class, you now inherit from System.ValueType instead of directly from System.Object. This causes a fundamental change in your object as they now exist on the stack instead of the heap. It also means they are now passed by value instead of reference, which means whenever you do an assignment the fields of one struct are automatically copied to the member fields of the destination struct, this is a shallow copy as any members that are reference types will just have the reference copied and not the referenced object.
Now, you may wonder what you gain from all of this, especially since now you have just doubled your memory footprint (or tripled depending on how many variables you keep.) The first big advantage is that the stack is collected as soon as it goes out of scope, so you never have to tell the GC when to do a cleanup. Your object is is auto-collected as soon as its scope is lost. There are some caveats to using structs you have to be prepared for as well.
Not always the best answer
Though you can define constructors, you cannot define a default constructor (you can get around this by defining a static factory method or a static read-only member and assigning it upon creation.) Also, a struct cannot be finalized and thus cannot implement a finalizer, so if they have any members they must be other ValueTypes, or references that either need no cleanup, or are created and destroyed outside the scope of the struct. Lastly, if you want to be able to pass a struct through or out of the parameter list of a function, you have to pass it by reference using the ref or out parameter modifiers. This could mean a fundamental change to your code and possibly and future API's based off of it.
Assignment and this operators
Structs also have one other property that separates them from classes. Even though both provide a this moniker to use as a reference to themselves, inside a struct this is writable. You can assign another instance of a struct to itself and quickly change all of the underlying values.
Shallow copies allow for static members to be defined that represent default values, and these values can be assigned to the struct during both construction or via a member method. This can drastically speed up programming if the struct has many internal members that are constantly assigned similar values. Although this uses more memory in the short term (stack) it is cleaned up much quicker because the objects have no risk of getting past generation 0 of the GarbageCollector and into a mid life crisis problem of cleanup.
Boxing
Inheritance and object references are another issue entirely and lead to a situation called boxing. Structs are sealed ValueType objects so they can implement interfaces and descend from the System.Object base type. Structs may also be boxed into references.
Boxing a value type converts it from a stack object to a heap reference. This happens whenever a struct is cast as an object or one of its interfaces. An odd side effect of this is that when a struct is boxed, a copy of it is converted to a reference. This copy can be modified and used like any other reference and even un-boxed back into a struct, however any changes done to the boxed copy are not reflected in the original struct or any earlier un-boxed version. The process of boxing and un-boxing a struct adds extra operations to your code so you should not do this often, though sometimes this is unavoidable.
Outside of the above, structs have all of the features of classes and some unique benefits that can improve the overall quality of a program, though if used late in development can come at a cost of restructuring or API changes.
Internal types of VistaDB
Internally we have found a number of locations in our own typing system (which are currently classes) can become pinned in memory by user code. This is obviously not a good situation for the engine since it cannot dispose of user references. One of the possible solutions we have discussed internally is converting some (or all) of these VistaDBTypes into structs.
Similar Posts
- The GC does not solve all memory leaks
- SQL Server 2008 (Katmai) Information
- Set any 2008 Resolutions or goals?
