BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

.NET Framework
.NET 4.0+

Lazy Initialisation with Lazy<T>

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.

Lazy Initialisation

Some objects can be very expensive to instantiate. This can be in terms of performance, when the construction of an object takes a long time, perhaps because of communication with an external service or database. Some objects are expensive in terms of resources, perhaps because whilst they exist they consume relatively large amounts of memory. This type of object can have an impact upon the performance and usability of your software that should be minimised.

It can be useful to only create expensive objects at the time that they are actually used. In some cases this will delay the performance problem or resource usage. In others, for example where the user decides whether or not to perform an action that requires the item, it may be that the object is never used so the expense is never incurred. For example, you may create a file management tool that performs a slow verification process for each file in a folder and shows the results to the user when they select the file. If you delay the process until the user selects the file, you might only need to run it against a few chosen items, rather than every file present.

A standard way of delaying the creation of objects is to use lazy initialisation. With lazy initialisation, or lazy initialization, some means of identifying whether or not a value has been constructed is maintained. This is often a Boolean value that is set when the object is created, or you may a value in a field that remains null until it is first accessed via a method or property. When the item is required for the first time, the expensive initialisation process occurs and the newly initialised object is stored. Future requests for the value return the stored object.

Prior to the availability of the .NET framework version 4.0, C# developers needed to implement their own lazy initialisation process. The code below shows an example implementation for a property of a test class. On instantiation of a TestClass object, the SlowToLoad object is not initialised. Only when the property is accessed is the new object created, stored in a field and returned. Subsequent requests for the property return the previously created object.

NB: This is a simplified example that is not thread-safe. A production version would include locking to prevent multiple threads entering the initialisation code at the same time.

public class TestClass
{
    SlowToLoad _slow;

    public SlowToLoad Slow
    {
        get
        {
            if (_slow == null)
            {
                _slow = new SlowToLoad();
            }
            return _slow;
        }
    }
}

Lazy<T>

In the .NET framework version 4.0, the lazy initialisation pattern has been encapsulated in a reusable, generic class named, Lazy<T>. This class provides a wrapper for an object that is slow or expensive to create. It takes care of the initialisation process, guaranteeing that the object is not constructed unless it is actually requested. It also ensures that the initialisation can be performed in a thread-safe manner.

Creating a Simple Lazy<T> Instance

The simplest way to use Lazy<T> is to wrap an object of a class that includes a default constructor. To do so, you simple instantiate a new Lazy<T> using its own default constructor. When the object is first requested, a new object of the wrapped type is created, stored and returned.

Before we can demonstrate this we need a class that represents our expensive type. We'll use the code shown below. This isn't really a slow object but will suffice for the purpose of this article. You should imagine that the Data property is loaded with information from an external service using a process that may take several seconds. Note that the default constructor outputs a message to the console. We'll use this message to show the time at which SlowToLoad objects are generated.

public class SlowToLoad
{
    public SlowToLoad()
    {
        Console.WriteLine("Data Loaded");
        Data = "ABC";
    }

    public SlowToLoad(string data)
    {
        Data = data;
    }

    public string Data { get; set; }
}

We can now create a lazily initialised version of SlowToLoad, as shown below. When you run this code you'll see the message shown below but not the "Data Loaded" message from the SlowToLoad class. As we haven't accessed the value of the Lazy<T> object, the wrapped SlowToLoad object never needs to be created.

Lazy<SlowToLoad> lazySlow = new Lazy<SlowToLoad>();
Console.WriteLine("Lazy Object Initialised");

/* OUTPUT

Lazy Object Initialised

*/

When you want to access the value of the wrapped object you can do so using the Value property. When this property is read for the first time, the initialisation process is executed. We can see this from the output of the following example:

Lazy<SlowToLoad> lazySlow = new Lazy<SlowToLoad>();
Console.WriteLine("Lazy Object Initialised");
Console.WriteLine(lazySlow.Value.Data);

/* OUTPUT

Lazy Object Initialised
Data Loaded
ABC

*/

Importantly, the Lazy<T> class will not run the initialisation process twice. After the first access of the Value property, subsequent reads return the previously created object. If two threads attempt to run the initialisation simultaneously, one will be blocked until the wrapped object is created. Both will then return the value created by the first thread.

We can see the single initialisation of the object in the following example, where the "Data Loaded" message only appears once, despite the value being read twice.

Lazy<SlowToLoad> lazySlow = new Lazy<SlowToLoad>();
Console.WriteLine("Lazy Object Initialised");
Console.WriteLine(lazySlow.Value.Data);
Console.WriteLine(lazySlow.Value.Data);

/* OUTPUT

Lazy Object Initialised
Data Loaded
ABC
ABC

*/
30 August 2012