Lazy initialization or lazy loading, is the idea that the object will not be constructed, created, initialized or loaded until it is absolutely needed. A common scenario for lazy initialization is a list that stored in a cache is empty until it is accessed, then it is loaded perhaps from a record-set in a database or file and then placed into cached and finally returned back to the caller. If the list is never requested, then the cache is never filled and the application’s working set is smaller. Typically smaller working sets (of memory) mean better perceived performance and happier customers.
System.Lazy<T> provides a (potentially) thread safe wrapper around an object and provides a means of deferring initialization of the core object until it’s requested via Func<T> delegate. Depending upon the constructor called, you can emulate one or more different locking techniques such as doubled checked locking, using a nested class to defer initialization or by doing initialization via a locking mechanism when the object is first accessed. In memory challenged situations, the System.Threading.LazyInitializer class provides static methods for doing thread-safe (potentially) lazy initialization as well.
I included thread safety “potentially” in parenthesis above because depending upon the constructor overload or method overload used on Lazy<T> thread safety may or may not be ensured. The reason this is so is to improve performance when thread safety is not required or desired because no synchronization locking is done.
Lazy Initialized Singletons
One of the commonest examples of lazy initialization are implementations of the GOF pattern, the Singleton. There are several ways to implement the singleton pattern in C# using lazy initialization, from extremely simple to somewhat complex. Each way offers differences in thread synchronization schematics used, the relative thread safety, and the “laziness” of the implementation. Besides the traditional way of implementing singletons, you can also use one of the new .NET 4 classes System.Lazy<T> or System.Threading.LazyInitializer class.
Their are at least four thread-safe variants of the singleton available to languages targeting the CLR with three of them supporting directly lazy initialization. In a nutshell they are the double-checked locking techniquewhich is broken in Java, but works accurately in C# , and full lazy instantiation using a nested inner class with a private static constructor and a reference to an internal static readonly instance of the parent class, and a third version shows thread safe lazy initialization with the caveat of reduced performance since a lock is acquired on all reads. For an in-depth discussion check out Jon Skeet’s excellent article on the topic.
Using the System.Lazy<T> class in .NET 4.0 you can easily implement the singleton pattern in a thread safe, lazy initialized manner that is optimized for performance:
Note that passing in “true” to the constructor makes the initialization thread safe by using double locked schematics described above. If “false” is passed in for isThreadSafe, then no synchronization takes place. You can also use one of the following LazyThreadSafetyMode enumerations in another overloaded constructor call:
- LazyThreadSafetyMode.None – no synchronization occurs (not thread safe)
- LazyThreadSafetyMode.Publication – uses Interlocked.CompareExchange
- LazyThreadSafetyMode.ExecutionAndPublication – uses the C# lock keyword which the CLR interprets as a Monitor (note in Richter’s book he states that it uses double checked locking, but reflector shows only one lock…)
Here is another example using System.Threading.LazyInitializer:
Lazy Initialization and Micro Optimizations
Now one might argue that lazy initialization is a premature if not unnecessary micro-optimization. In some respects that is probably true. Singletons, for instance, are only created once for the entire lifetime of an application, typically at start up. However, if you truly need thread safety, lazy initialization then System.Lazy or System.Threading.LazyInitializer are the way to go with .NET 4.
- CLR via C# by Richter, Jeffry 2010 Microsoft Press