//
Lazy initialization of an object means that its creation is deferred until it is first used. (For this topic, the terms lazy initialization and lazy instantiation are synonymous.) Lazy initialization is primarily used to improve performance, avoid wasteful computation, and reduce program memory requirements. These are the most common scenarios:
-
When you have an object that is expensive to create, and the program might not use it. For example, assume that you have in memory a
Customer
object that has anOrders
property that contains a large array ofOrder
objects that, to be initialized, requires a database connection. If the user never asks to display the Orders or use the data in a computation, then there is no reason to use system memory or computing cycles to create it. By usingLazy<Orders>
to declare theOrders
object for lazy initialization, you can avoid wasting system resources when the object is not used. -
When you have an object that is expensive to create, and you want to defer its creation until after other expensive operations have been completed. For example, assume that your program loads several object instances when it starts, but only some of them are required immediately. You can improve the startup performance of the program by deferring initialization of the objects that are not required until the required objects have been created.
Although you can write your own code to perform lazy initialization, we recommend that you use Lazy<T> instead. Lazy<T> and its related types also support thread-safety and provide a consistent exception propagation policy.
The following table lists the types that the .NET Framework version 4 provides to enable lazy initialization in different scenarios.
Expand table
Type | Description |
---|---|
Lazy<T> | A wrapper class that provides lazy initialization semantics for any class library or user-defined type. |
ThreadLocal<T> | Resembles Lazy<T> except that it provides lazy initialization semantics on a thread-local basis. Every thread has access to its own unique value. |
LazyInitializer | Provides advanced static (Shared in Visual Basic) methods for lazy initialization of objects without the overhead of a class. |
Basic Lazy Initialization
To define a lazy-initialized type, for example, MyType
, use Lazy<MyType>
(Lazy(Of MyType)
in Visual Basic), as shown in the following example. If no delegate is passed in the Lazy<T> constructor, the wrapped type is created by using Activator.CreateInstance when the value property is first accessed. If the type does not have a parameterless constructor, a run-time exception is thrown.
In the following example, assume that Orders
is a class that contains an array of Order
objects retrieved from a database. A Customer
object contains an instance of Orders
, but depending on user actions, the data from the Orders
object might not be required.
C#Copy
// Initialize by using default Lazy<T> constructor. The
// Orders array itself is not created yet.
Lazy<Orders> _orders = new Lazy<Orders>();
You can also pass a delegate in the Lazy<T> constructor that invokes a specific constructor overload on the wrapped type at creation time, and perform any other initialization steps that are required, as shown in the following example.
C#Copy
// Initialize by invoking a specific constructor on Order when Value
// property is accessed
Lazy<Orders> _orders = new Lazy<Orders>(() => new Orders(100));
After the Lazy object is created, no instance of Orders
is created until the Value property of the Lazy variable is accessed for the first time. On first access, the wrapped type is created and returned, and stored for any future access.
C#Copy
// We need to create the array only if displayOrders is true
if (displayOrders == true)
{
DisplayOrders(_orders.Value.OrderData);
}
else
{
// Don't waste resources getting order data.
}
A Lazy<T> object always returns the same object or value that it was initialized with. Therefore, the Value property is read-only. If Value stores a reference type, you cannot assign a new object to it. (However, you can change the value of its settable public fields and properties.) If Value stores a value type, you cannot modify its value. Nevertheless, you can create a new variable by invoking the variable constructor again by using new arguments.
C#Copy
_orders = new Lazy<Orders>(() => new Orders(10));
The new lazy instance, like the earlier one, does not instantiate Orders
until its Value property is first accessed.
Thread-Safe Initialization
By default, Lazy<T> objects are thread-safe. That is, if the constructor does not specify the kind of thread safety, the Lazy<T> objects it creates are thread-safe. In multi-threaded scenarios, the first thread to access the Value property of a thread-safe Lazy<T> object initializes it for all subsequent accesses on all threads, and all threads share the same data. Therefore, it does not matter which thread initializes the object, and race conditions are benign.
Note
You can extend this consistency to error conditions by using exception caching. For more information, see the next section, Exceptions in Lazy Objects.
The following example shows that the same Lazy<int>
instance has the same value for three separate threads.
C#Copy
// Initialize the integer to the managed thread id of the
// first thread that accesses the Value property.
Lazy<int> number = new Lazy<int>(() => Thread.CurrentThread.ManagedThreadId);
Thread t1 = new Thread(() => Console.WriteLine("number on t1 = {0} ThreadID = {1}",
number.Value, Thread.CurrentThread.ManagedThreadId));
t1.Start();
Thread t2 = new Thread(() => Console.WriteLine("number on t2 = {0} ThreadID = {1}",
number.Value, Thread.CurrentThread.ManagedThreadId));
t2.Start();
Thread t3 = new Thread(() => Console.WriteLine("number on t3 = {0} ThreadID = {1}", number.Value,
Thread.CurrentThread.ManagedThreadId));
t3.Start();
// Ensure that thread IDs are not recycled if the
// first thread completes before the last one starts.
t1.Join();
t2.Join();
t3.Join();
/* Sample Output:
number on t1 = 11 ThreadID = 11
number on t3 = 11 ThreadID = 13
number on t2 = 11 ThreadID = 12
Press any key to exit.
*/
If you require separate data on each thread, use the ThreadLocal<T> type, as described later in this topic.
Some Lazy<T> constructors have a Boolean parameter named isThreadSafe
that is used to specify whether the Value property will be accessed from multiple threads. If you intend to access the property from just one thread, pass in false
to obtain a modest performance benefit. If you intend to access the property from multiple threads, pass in true
to instruct the Lazy<T> instance to correctly handle race conditions in which one thread throws an exception at initialization time.
Some Lazy<T> constructors have a LazyThreadSafetyMode parameter named mode
. These constructors provide an additional thread safety mode. The following table shows how the thread safety of a Lazy<T> object is affected by constructor parameters that specify thread safety. Each constructor has at most one such parameter.
Expand table
Thread safety of the object | LazyThreadSafetyMode mode parameter | Boolean isThreadSafe parameter | No thread safety parameters |
---|---|---|---|
Fully thread-safe; only one thread at a time tries to initialize the value. | ExecutionAndPublication | true | Yes. |
Not thread-safe. | None | false | Not applicable. |
Fully thread-safe; threads race to initialize the value. | PublicationOnly | Not applicable. | Not applicable. |
As the table shows, specifying LazyThreadSafetyMode.ExecutionAndPublication for the mode
parameter is the same as specifying true
for the isThreadSafe
parameter, and specifying LazyThreadSafetyMode.None is the same as specifying false
.
For more information about what Execution
and Publication
refer to, see LazyThreadSafetyMode.
Specifying LazyThreadSafetyMode.PublicationOnly allows multiple threads to attempt to initialize the Lazy<T> instance. Only one thread can win this race, and all the other threads receive the value that was initialized by the successful thread. If an exception is thrown on a thread during initialization, that thread does not receive the value set by the successful thread. Exceptions are not cached, so a subsequent attempt to access the Value property can result in successful initialization. This differs from the way exceptions are treated in other modes, which is described in the following section. For more information, see the LazyThreadSafetyMode enumeration.
Exceptions in Lazy Objects
As stated earlier, a Lazy<T> object always returns the same object or value that it was initialized with, and therefore the Value property is read-only. If you enable exception caching, this immutability also extends to exception behavior. If a lazy-initialized object has exception caching enabled and throws an exception from its initialization method when the Value property is first accessed, that same exception is thrown on every subsequent attempt to access the Value property. In other words, the constructor of the wrapped type is never re-invoked, even in multithreaded scenarios. Therefore, the Lazy<T> object cannot throw an exception on one access and return a value on a subsequent access.
Exception caching is enabled when you use any System.Lazy<T> constructor that takes an initialization method (valueFactory
parameter); for example, it is enabled when you use the Lazy(T)(Func(T))
constructor. If the constructor also takes a LazyThreadSafetyMode value (mode
parameter), specify LazyThreadSafetyMode.ExecutionAndPublication or LazyThreadSafetyMode.None. Specifying an initialization method enables exception caching for these two modes. The initialization method can be very simple. For example, it might call the parameterless constructor for T
: new Lazy<Contents>(() => new Contents(), mode)
in C#, or New Lazy(Of Contents)(Function() New Contents())
in Visual Basic. If you use a System.Lazy<T> constructor that does not specify an initialization method, exceptions that are thrown by the parameterless constructor for T
are not cached. For more information, see the LazyThreadSafetyMode enumeration.
Note
If you create a Lazy<T> object with the isThreadSafe
constructor parameter set to false
or the mode
constructor parameter set to LazyThreadSafetyMode.None, you must access the Lazy<T> object from a single thread or provide your own synchronization. This applies to all aspects of the object, including exception caching.
As noted in the previous section, Lazy<T> objects created by specifying LazyThreadSafetyMode.PublicationOnly treat exceptions differently. With PublicationOnly, multiple threads can compete to initialize the Lazy<T> instance. In this case, exceptions are not cached, and attempts to access the Value property can continue until initialization is successful.
The following table summarizes the way the Lazy<T> constructors control exception caching.
Expand table
Constructor | Thread safety mode | Uses initialization method | Exceptions are cached |
---|---|---|---|
Lazy(T)() | (ExecutionAndPublication) | No | No |
Lazy(T)(Func(T)) | (ExecutionAndPublication) | Yes | Yes |
Lazy(T)(Boolean) | True (ExecutionAndPublication) or false (None) | No | No |
Lazy(T)(Func(T), Boolean) | True (ExecutionAndPublication) or false (None) | Yes | Yes |
Lazy(T)(LazyThreadSafetyMode) | User-specified | No | No |
Lazy(T)(Func(T), LazyThreadSafetyMode) | User-specified | Yes | No if user specifies PublicationOnly; otherwise, yes. |
Implementing a Lazy-Initialized Property
To implement a public property by using lazy initialization, define the backing field of the property as a Lazy<T>, and return the Value property from the get
accessor of the property.
C#Copy
class Customer
{
private Lazy<Orders> _orders;
public string CustomerID {get; private set;}
public Customer(string id)
{
CustomerID = id;
_orders = new Lazy<Orders>(() =>
{
// You can specify any additional
// initialization steps here.
return new Orders(this.CustomerID);
});
}
public Orders MyOrders
{
get
{
// Orders is created on first access here.
return _orders.Value;
}
}
}
The Value property is read-only; therefore, the property that exposes it has no set
accessor. If you require a read/write property backed by a Lazy<T> object, the set
accessor must create a new Lazy<T> object and assign it to the backing store. The set
accessor must create a lambda expression that returns the new property value that was passed to the set
accessor, and pass that lambda expression to the constructor for the new Lazy<T> object. The next access of the Value property will cause initialization of the new Lazy<T>, and its Value property will thereafter return the new value that was assigned to the property. The reason for this convoluted arrangement is to preserve the multithreading protections built into Lazy<T>. Otherwise, the property accessors would have to cache the first value returned by the Value property and only modify the cached value, and you would have to write your own thread-safe code to do that. Because of the additional initializations required by a read/write property backed by a Lazy<T> object, the performance might not be acceptable. Furthermore, depending on the specific scenario, additional coordination might be required to avoid race conditions between setters and getters.
Thread-Local Lazy Initialization
In some multithreaded scenarios, you might want to give each thread its own private data. Such data is called thread-local data. In the .NET Framework version 3.5 and earlier, you could apply the ThreadStatic
attribute to a static variable to make it thread-local. However, using the ThreadStatic
attribute can lead to subtle errors. For example, even basic initialization statements will cause the variable to be initialized only on the first thread that accesses it, as shown in the following example.
C#Copy
[ThreadStatic]
static int counter = 1;
On all other threads, the variable will be initialized by using its default value (zero). As an alternative in the .NET Framework version 4, you can use the System.Threading.ThreadLocal<T> type to create an instance-based, thread-local variable that is initialized on all threads by the Action<T> delegate that you provide. In the following example, all threads that access counter
will see its starting value as 1.
C#Copy
ThreadLocal<int> betterCounter = new ThreadLocal<int>(() => 1);
ThreadLocal<T> wraps its object in much the same way as Lazy<T>, with these essential differences:
-
Each thread initializes the thread-local variable by using its own private data that is not accessible from other threads.
-
The ThreadLocal<T>.Value property is read-write, and can be modified any number of times. This can affect exception propagation, for example, one
get
operation can raise an exception but the next one can successfully initialize the value. -
If no initialization delegate is provided, ThreadLocal<T> will initialize its wrapped type by using the default value of the type. In this regard, ThreadLocal<T> is consistent with the ThreadStaticAttribute attribute.
The following example demonstrates that every thread that accesses the ThreadLocal<int>
instance gets its own unique copy of the data.
C#Copy
// Initialize the integer to the managed thread id on a per-thread basis.
ThreadLocal<int> threadLocalNumber = new ThreadLocal<int>(() => Thread.CurrentThread.ManagedThreadId);
Thread t4 = new Thread(() => Console.WriteLine("threadLocalNumber on t4 = {0} ThreadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId));
t4.Start();
Thread t5 = new Thread(() => Console.WriteLine("threadLocalNumber on t5 = {0} ThreadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId));
t5.Start();
Thread t6 = new Thread(() => Console.WriteLine("threadLocalNumber on t6 = {0} ThreadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId));
t6.Start();
// Ensure that thread IDs are not recycled if the
// first thread completes before the last one starts.
t4.Join();
t5.Join();
t6.Join();
/* Sample Output:
threadLocalNumber on t4 = 14 ThreadID = 14
threadLocalNumber on t5 = 15 ThreadID = 15
threadLocalNumber on t6 = 16 ThreadID = 16
*/
Thread-Local Variables in Parallel.For and ForEach
When you use the Parallel.For method or Parallel.ForEach method to iterate over data sources in parallel, you can use the overloads that have built-in support for thread-local data. In these methods, the thread-locality is achieved by using local delegates to create, access, and clean up the data. For more information, see How to: Write a Parallel.For Loop with Thread-Local Variables and How to: Write a Parallel.ForEach Loop with Partition-Local Variables.
Using Lazy Initialization for Low-Overhead Scenarios
In scenarios where you have to lazy-initialize a large number of objects, you might decide that wrapping each object in a Lazy<T> requires too much memory or too many computing resources. Or, you might have stringent requirements about how lazy initialization is exposed. In such cases, you can use the static
(Shared
in Visual Basic) methods of the System.Threading.LazyInitializer class to lazy-initialize each object without wrapping it in an instance of Lazy<T>.
In the following example, assume that, instead of wrapping an entire Orders
object in one Lazy<T> object, you have lazy-initialized individual Order
objects only if they are required.
C#Copy
// Assume that _orders contains null values, and
// we only need to initialize them if displayOrderInfo is true
if (displayOrderInfo == true)
{
for (int i = 0; i < _orders.Length; i++)
{
// Lazily initialize the orders without wrapping them in a Lazy<T>
LazyInitializer.EnsureInitialized(ref _orders[i], () =>
{
// Returns the value that will be placed in the ref parameter.
return GetOrderForIndex(i);
});
}
}
In this example, notice that the initialization procedure is invoked on every iteration of the loop. In multi-threaded scenarios, the first thread to invoke the initialization procedure is the one whose value is seen by all threads. Later threads also invoke the initialization procedure, but their results are not used. If this kind of potential race condition is not acceptable, use the overload of LazyInitializer.EnsureInitialized that takes a Boolean argument and a synchronization object.
How to: Perform Lazy Initialization of Objects
The System.Lazy<T> class simplifies the work of performing lazy initialization and instantiation of objects. By initializing objects in a lazy manner, you can avoid having to create them at all if they are never needed, or you can postpone their initialization until they are first accessed. For more information, see Lazy Initialization.
Example 1
The following example shows how to initialize a value with Lazy<T>. Assume that the lazy variable might not be needed, depending on some other code that sets the someCondition
variable to true or false.
C#Copy
static bool someCondition = false;
//Initializing a value with a big computation, computed in parallel
Lazy<int> _data = new Lazy<int>(delegate
{
return ParallelEnumerable.Range(0, 1000).
Select(i => Compute(i)).Aggregate((x,y) => x + y);
}, LazyThreadSafetyMode.ExecutionAndPublication);
// Do some work that may or may not set someCondition to true.
// ...
// Initialize the data only if necessary
if (someCondition)
{
if (_data.Value > 100)
{
Console.WriteLine("Good data");
}
}
Example 2
The following example shows how to use the System.Threading.ThreadLocal<T> class to initialize a type that is visible only to the current object instance on the current thread.
C#Copy
//Initializing a value per thread, per instance
ThreadLocal<int[][]> _scratchArrays =
new ThreadLocal<int[][]>(InitializeArrays);
// . . .
static int[][] InitializeArrays () {return new int[][]}
// . . .
// use the thread-local data
int i = 8;
int [] tempArr = _scratchArrays.Value[i];
//