Garbage collection is obviously a core function of the runtime. Its purpose is to restore memory consumed by objects that are no longer referenced. The emphasis in this statement is on memory and references: The garbage collector is responsible only for restoring memory; it does not handle other resources such as database connections, handles (files, windows, etc.), network ports, and hardware devices such as serial ports. Also, the garbage collector determines what to clean up, based on whether any references remain. Implicitly, this means that the garbage collector works with reference objects and restores memory on the heap only. Additionally, it means that maintaining a reference to an object will delay the garbage collector from reusing the memory consumed by the object.
All references discussed so far are strong references because they maintain an object’s accessibility and prevent the garbage collector from cleaning up the memory consumed by the object. The framework also supports the concept of weak references. Weak references do not prevent garbage collection on an object, but they do maintain a reference so that if the garbage collector does not clean up the object, it can be reused.
Weak references are designed for reference objects that are expensive to create, yet too expensive to keep around. Consider, for example, a large list of objects loaded from a database and displayed to the user. The loading of this list is potentially expensive, and once the user closes the list, it should be available for garbage collection. However, if the user requests the list multiple times, a second expensive load call will always be required. With weak references, it becomes possible to use code to check whether the list has been cleaned up, and if not, to re-reference the same list. In this way, weak references serve as a memory cache for objects. Objects within the cache are retrieved quickly, but if the garbage collector has recovered the memory of these objects, they will need to be re-created.
Once a reference object (or collection of objects) is recognized as worthy of potential weak reference consideration, it needs to be assigned to System.WeakReference (see Listing 10.14).
Admittedly, this code uses generics, which aren’t discussed in this book until Chapter 12. However, you can safely ignore the <byte> text both when declaring the Data property and when assigning it. While there is a nongeneric version of WeakReference, there is little reason to consider it.8
The bulk of the logic appears in the GetData() method. The purpose of this method is to always return an instance of the data—whether from the cache or by reloading it. GetData() begins by checking whether the Data property is null. If it is, the data is loaded and assigned to a local variable called target. This creates a reference to the data so that the garbage collector will not clear it. Next, we instantiate a WeakReference and pass a reference to the loaded data so that the WeakReference object has a handle to the data (its target); then, if requested, such an instance can be returned. Do not pass an instance that does not have a local reference to WeakReference, because it might get cleaned up before you have a chance to return it (i.e., do not call new WeakReference<byte>(LoadData())).
If the Data property already has an instance of WeakReference, then the code calls TryGetTarget() and, if there is an instance, assigns target, thus creating a reference so that the garbage collector will no longer clean up the data.
Lastly, if WeakReference’s TryGetTarget() method returns false, we load the data, assign the reference with a call to SetTarget(), and return the newly instantiated object.