Lazy Initialization

In the preceding section, we discussed how to deterministically dispose of an object with a using statement and how the finalization queue will dispose of resources in the event that no deterministic approach is used.

A related pattern is called lazy initialization or lazy loading. Using lazy initialization, you can create (or obtain) objects when you need them rather than beforehand—the latter can be an especially problematic situation when those objects are never used. Consider the FileStream property of Listing 10.19.

Listing 10.19: Lazy Loading a Property
class DataCache
{
    // ...
 
    public TemporaryFileStream FileStream =>
        InternalFileStream ??= 
            new TemporaryFileStream();
 
    private TemporaryFileStream? InternalFileStream
        { getset; } = null;
 
 
    // ...
}

In the FileStream expression bodied property, we check whether InternalFileStream is null before returning its value directly. If InternalFileStream is null, we first instantiate the TemporaryFileStream object and assign it to InternalFileStream before returning the new instance. Thus, the TemporaryFileStream required in the FileStream property is created only when the getter on the property is called. If the getter is never invoked, the TemporaryFileStream object would not be instantiated and we would save whatever execution time such an instantiation would cost. Obviously, if the instantiation is negligible or inevitable (and postponing the inevitable is less desirable), simply assigning it during declaration or in the constructor makes sense.

AdVanced Topic
Lazy Loading with Generics and Lambda Expressions

Listing 10.20 demonstrates how to use System.Lazy<T> to assist with lazy initialization.12

Listing 10.20: Lazy Loading a Property with System.Lazy<T>
class DataCache
{
    // ...
 
    public TemporaryFileStream FileStream => 
        InternalFileStream.Value;
    private Lazy<TemporaryFileStream> InternalFileStream { get; }
        = new Lazy<TemporaryFileStream>( 
            () => new TemporaryFileStream() );
 
    // ...
}

The System.Lazy<T> class takes a type parameter (T) that identifies which type the Value property on System.Lazy<T> will return. Instead of assigning a fully constructed TemporaryFileStream to the _FileStream field, an instance of Lazy<TemporaryFileStream> is assigned (a lightweight call), delaying the instantiation of the TemporaryFileStream itself until the Value property (and therefore the FileStream property) is accessed.

If you use delegates in addition to type parameters (generics), you can even provide a function for how to initialize an object when the Value property is accessed. Listing 10.20 demonstrates passing the delegate—a lambda expression in this case—into the constructor for System.Lazy<T>.

Note that the lambda expression itself, () => new TemporaryFileStream(FileStreamName), does not execute until Value is called. Rather, the lambda expression provides a means of passing the instructions for what will happen; it does not actually execute those instructions until explicitly requested to do so.

One obvious question is when you should use the System.Lazy<T> rather than the approach outlined in Listing 10.19. The difference is negligible: In fact, Listing 10.19 may actually be simpler. That is, it is simpler until multiple threads are involved, such that a race condition might occur regarding the instantiation. In Listing 10.19, more than one check for null might potentially occur before instantiation, resulting in multiple instances being created. In contrast, System.Lazy<T> provides a thread-safe mechanism ensuring that one and only one object will be created.

Chapter 10: Summary

This chapter provided a whirlwind tour of many topics related to building solid class libraries. All the topics pertain to internal development as well, but they are much more critical to building robust classes. Ultimately, the focus here was on forming more robust and programmable APIs. In the category of robustness, we can include namespaces and garbage collection. Both of these topics fit in the programmability category as well, along with overriding object’s virtual members, operator overloading, and XML comments for documentation.

Exception handling heavily depends on inheritance, by defining an exception hierarchy and enforcing custom exceptions to fit within this hierarchy. Furthermore, the C# compiler uses inheritance to verify catch block order. In Chapter 11, you will see why inheritance is such a core part of exception handling.

________________________________________

12. Class added to the CLR starting with C# 4.0 and Microsoft .NET Framework 4.0.
{{ snackbarMessage }}
;