Lazy Initialisation with Lazy<T> (2)
When working with resource intensive objects or types that are slow to instantiate, especially where those objects may not be required, it can be useful to only create the objects when first needed. This technique is known as lazy initialisation.
Initialising Complex Objects
Often the type to which you wish to add lazy initialisation not have a default constructor, or you may wish to use an alternative constructor. For these cases you can create a Lazy<T> object and provide a Func delegate that performs the initialisation. The delegate may simply instantiate and return the object or it may include some additional code. It must return an object of the type defined in the Lazy<T>'s type parameter.
In the sample below we use a lambda expression as the argument to the Lazy<SlowToLoad> class's constructor. The lambda outputs a message indicating that the data has been loaded and returns a new SlowToLoad object with its Data property set to "XYZ". This function is called when the Lazy<T>'s Value property is first accessed, as can be seen from the order of the outputted messages.
Lazy<SlowToLoad> lazySlow = new Lazy<SlowToLoad>(() =>
return new SlowToLoad("XYZ");
Console.WriteLine("Lazy Object Initialised");
Lazy Object Initialised
NB: Using a Func delegate to initialise a Lazy<T> instance generates a closure. You should take care, as any changes to the values of captured variables that happen before the Value property is accessed will affect the behaviour of the function.
Checking if a Value is Present
The other property of Lazy<T> that is useful is IsValueCreated. This returns a Boolean value that is true if the wrapped object has been initialised and false otherwise. It is important when you need to perform an action based upon the status of the wrapped object but without running the initialisation process unnecessarily. For example, you may have a collection of lazily initialised objects, each of which implements IDisposable to release unmanaged resources. You could loop through such a collection, checking the IsValueCreated flag and only executing the Dispose method for those where the property returns true.
The following example shows the value of IsValueCreated before and after initialisation.
Lazy<SlowToLoad> lazySlow = new Lazy<SlowToLoad>();
As mentioned previously, by default the Lazy<T> class enforces thread-safety. Only one thread is permitted to access the initialisation code for the wrapped object. Other threads are blocked until the Value property has been set, at which point they return the value set by the first thread. In some rare situations you may want to disable the thread-safety. This is usually to achieve improved performance and should only be done if you are certain that only one thread will ever try to access the object's value at any time.
The simplest way to disable thread-safety is to pass a Boolean argument to the Lazy<T> class's constructor. This is either as the only argument, when using the wrapped object's default constructor, or as the second parameter following a Func delegate. If the argument's value is true, thread-safety is enforced. If false, the locking mechanism is not used.
The following code creates a lazily initialised SlowToLoad object without thread-safety.
Lazy<SlowToLoad> lazySlow = new Lazy<SlowToLoad>(false);
For greater control over the threading options for a Lazy<T> object you can replace the Boolean parameter with one of the values from the LazyThreadSafetyMode enumeration. Three constants are available:
- None. The equivalent of passing false to the Boolean parameter. No thread-safety is implemented. The result of two threads entering the initialisation code simultaneously are undefined so you should not permit this to happen.
- PublicationOnly. With this option multiple threads may enter the initialisation function or the default constructor of the wrapped type. The first thread to complete sets the Value property. All of the other threads discard their results and return the value set by the first thread.
- ExecutionAndPublication. The equivalent of passing true to the Boolean parameter. Only one thread is permitted access to the initialisation function or wrapped type's default constructor. As locking is used, if the initialisation process adds its own locks, you should take care to avoid deadlocks.
Lazy<SlowToLoad> lazySlow = new Lazy<SlowToLoad>(LazyThreadSafetyMode.PublicationOnly);
30 August 2012