Language
Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
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
Customerobject that has anOrdersproperty that contains a large array ofOrderobjects 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 usingLazyto declare theOrdersobject 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
The following table lists the types that the .NET Framework version 4 provides to enable lazy initialization in different scenarios.
| Type | Description |
|---|---|
| Lazy |
A wrapper class that provides lazy initialization semantics for any class library or user-defined type. |
| ThreadLocal |
Resembles Lazy |
| 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 (Lazy(Of MyType) in Visual Basic), as shown in the following example. If no delegate is passed in the Lazy
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.
// Initialize by using default Lazy constructor. The
// Orders array itself is not created yet.
Lazy _orders = new();
' Initialize by using default Lazy constructor. The
'Orders array itself is not created yet.
Dim _orders As Lazy(Of Orders) = New Lazy(Of Orders)()
You can also pass a delegate in the Lazy
// Initialize by invoking a specific constructor on Order when Value
// property is accessed
Lazy _orders = new(() => new Orders(100));
' Initialize by invoking a specific constructor on Order
' when Value property is accessed
Dim _orders As Lazy(Of Orders) = New Lazy(Of Orders)(Function() 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.
// We need to create the array only if displayOrders is true
if (s_displayOrders == true)
{
DisplayOrders(_orders.Value.OrderData);
}
else
{
// Don't waste resources getting order data.
}
' We need to create the array only if _displayOrders is true
If _displayOrders = True Then
DisplayOrders(_orders.Value.OrderData)
Else
' Don't waste resources getting order data.
End If
A Lazy
_orders = new Lazy(() => new Orders(10));
_orders = New Lazy(Of Orders)(Function() 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
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 instance has the same value for three separate threads.
// Initialize the integer to the managed thread id of the
// first thread that accesses the Value property.
Lazy number = new(() => Environment.CurrentManagedThreadId);
Thread t1 = new(() => Console.WriteLine($"number on t1 = {number.Value} ThreadID = {Environment.CurrentManagedThreadId}"));
t1.Start();
Thread t2 = new(() => Console.WriteLine($"number on t2 = {number.Value} ThreadID = {Environment.CurrentManagedThreadId}"));
t2.Start();
Thread t3 = new(() => Console.WriteLine($"number on t3 = {number.Value} ThreadID = {Environment.CurrentManagedThreadId}"));
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.
*/
' Initialize the integer to the managed thread id of the
' first thread that accesses the Value property.
Dim number As Lazy(Of Integer) = New Lazy(Of Integer)(Function()
Return Thread.CurrentThread.ManagedThreadId
End Function)
Dim t1 As New Thread(Sub()
Console.WriteLine("number on t1 = {0} threadID = {1}",
number.Value, Thread.CurrentThread.ManagedThreadId)
End Sub)
t1.Start()
Dim t2 As New Thread(Sub()
Console.WriteLine("number on t2 = {0} threadID = {1}",
number.Value, Thread.CurrentThread.ManagedThreadId)
End Sub)
t2.Start()
Dim t3 As New Thread(Sub()
Console.WriteLine("number on t3 = {0} threadID = {1}",
number.Value, Thread.CurrentThread.ManagedThreadId)
End Sub)
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
Some LazyisThreadSafe 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
Some Lazymode. These constructors provide an additional thread safety mode. The following table shows how the thread safety of a Lazy
| 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
Exceptions in Lazy Objects
As stated earlier, a Lazy
Exception caching is enabled when you use any System.LazyvalueFactory 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 in C#, or New Lazy(Of Contents)(Function() New Contents()) in Visual Basic. If you use a System.LazyT are not cached. For more information, see the LazyThreadSafetyMode enumeration.
Note
If you create a LazyisThreadSafe constructor parameter set to false or the mode constructor parameter set to LazyThreadSafetyMode.None, you must access the Lazy
As noted in the previous section, Lazy
The following table summarizes the way the Lazy
| 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 Lazyget accessor of the property.
class Customer
{
private readonly Lazy _orders;
public string CustomerID { get; private set; }
public Customer(string id)
{
CustomerID = id;
_orders = new Lazy(() =>
{
// You can specify any additional
// initialization steps here.
return new Orders(CustomerID);
});
}
public Orders MyOrders
{
get
{
// Orders is created on first access here.
return _orders.Value;
}
}
}
Class Customer
Private _orders As Lazy(Of Orders)
Public Shared CustomerID As String
Public Sub New(ByVal id As String)
CustomerID = id
_orders = New Lazy(Of Orders)(Function()
' You can specify additional
' initialization steps here
Return New Orders(CustomerID)
End Function)
End Sub
Public ReadOnly Property MyOrders As Orders
Get
Return _orders.Value
End Get
End Property
End Class
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 Lazyset accessor must create a new Lazyset 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
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.
[ThreadStatic]
static int s_counter = 1;
Shared counter As Integer
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.ThreadLocalcounter will see its starting value as 1.
ThreadLocal _betterCounter = new(() => 1);
Dim betterCounter As ThreadLocal(Of Integer) = New ThreadLocal(Of Integer)(Function() 1)
ThreadLocal
Each thread initializes the thread-local variable by using its own private data that is not accessible from other threads.
The ThreadLocal
.Value property is read-write, and can be modified any number of times. This can affect exception propagation, for example, onegetoperation can raise an exception but the next one can successfully initialize the value.If no initialization delegate is provided, ThreadLocal
will initialize its wrapped type by using the default value of the type. In this regard, ThreadLocal is consistent with the ThreadStaticAttribute attribute.
The following example demonstrates that every thread that accesses the ThreadLocal instance gets its own unique copy of the data.
// Initialize the integer to the managed thread id on a per-thread basis.
ThreadLocal threadLocalNumber = new(() => Environment.CurrentManagedThreadId);
Thread t4 = new(() => Console.WriteLine($"threadLocalNumber on t4 = {threadLocalNumber.Value} ThreadID = {Environment.CurrentManagedThreadId}"));
t4.Start();
Thread t5 = new(() => Console.WriteLine($"threadLocalNumber on t5 = {threadLocalNumber.Value} ThreadID = {Environment.CurrentManagedThreadId}"));
t5.Start();
Thread t6 = new(() => Console.WriteLine($"threadLocalNumber on t6 = {threadLocalNumber.Value} ThreadID = {Environment.CurrentManagedThreadId}"));
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
*/
' Initialize the integer to the managed thread id on a per-thread basis.
Dim threadLocalNumber As New ThreadLocal(Of Integer)(Function() Thread.CurrentThread.ManagedThreadId)
Dim t4 As New Thread(Sub()
Console.WriteLine("number on t4 = {0} threadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId)
End Sub)
t4.Start()
Dim t5 As New Thread(Sub()
Console.WriteLine("number on t5 = {0} threadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId)
End Sub)
t5.Start()
Dim t6 As New Thread(Sub()
Console.WriteLine("number on t6 = {0} threadID = {1}",
threadLocalNumber.Value, Thread.CurrentThread.ManagedThreadId)
End Sub)
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 Lazystatic (Shared in Visual Basic) methods of the System.Threading.LazyInitializer class to lazy-initialize each object without wrapping it in an instance of Lazy
In the following example, assume that, instead of wrapping an entire Orders object in one LazyOrder objects only if they are required.
// 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
LazyInitializer.EnsureInitialized(ref _orders[i], () =>
{
// Returns the value that will be placed in the ref parameter.
return GetOrderForIndex(i);
});
}
}
' Assume that _orders contains null values, and
' we only need to initialize them if displayOrderInfo is true
If displayOrderInfo = True Then
For i As Integer = 0 To _orders.Length
' Lazily initialize the orders without wrapping them in a Lazy(Of T)
LazyInitializer.EnsureInitialized(_orders(i), Function()
' Returns the value that will be placed in the ref parameter.
Return GetOrderForIndex(i)
End Function)
Next
End If
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.