Lazy instantiation delays certain tasks. It typically improves the startup time of a C# application. This has always been possible to implement.
This type enables lazy loading (lazy instantiation) with a simple wrapper class
. It can reduce duplicated code that implements the needed logic.
In this program, you will first see the Test class
. This class
contains a constructor and an allocation occurs in the constructor.
Main
entry point, a new Lazy instance of the Test type is created. The IsValueCreated
property is False at this point.class
constructor is executed and memory allocation occurs.IsValueCreated
property returns True and the Test instance can be used like any other object.using System; class Test { int[] _array; public Test() { Console.WriteLine("Test()"); _array = new int[10]; } public int Length { get { return _array.Length; } } } class Program { static void Main() { // Create Lazy. Lazy<Test> lazy = new Lazy<Test>(); // Show that IsValueCreated is false. Console.WriteLine("IsValueCreated = {0}", lazy.IsValueCreated); // Get the Value. // ... This executes Test(). Test test = lazy.Value; // Show the IsValueCreated is true. Console.WriteLine("IsValueCreated = {0}", lazy.IsValueCreated); // The object can be used. Console.WriteLine("Length = {0}", test.Length); } }IsValueCreated = False Test() IsValueCreated = True Length = 10
There is substantial code complexity internal to the Lazy type and this will result in overhead and a performance penalty. So use Lazy on slower, hefty objects.
NumberAdder
instance each time. It accesses a field on the object.NumberAdder
. It shows the overhead of Lazy.using System; using System.Diagnostics; using System.IO; class NumberAdder { public int _result; public NumberAdder() { _result = 10 + 2; } } class Program { const int _max = 1000000; static void Main() { // Version 1: create an object each time. var s1 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { var number = new NumberAdder(); if (number._result != 12) { return; } } s1.Stop(); // Version 2: create Lazy, and access Value each time. var s2 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { var lazy = new Lazy<NumberAdder>(); if (lazy.Value._result != 12) { return; } } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); } } 5.09 ns new NumberAdder 68.24 ns new Lazy<NumberAdder>, get Value
Here is a benchmark that shows the performance benefit of Lazy. We have a class
NumberMultiplier
that is slow to create.
NumberMultiplier
instance each time it is needed.NumberMultiplier
, and then reuse the Lazy instance, for a performance gain.using System; using System.Diagnostics; class NumberMultiplier { public int _result; public NumberMultiplier() { var array = new int[1000]; for (int i = 0; i < array.Length; i++) { array[i] = i; array[i] *= i; _result = array[i]; } } } class Program { const int _max = 1000000; static void Main() { // Version 1: perform computation each time. var s1 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { var number = new NumberMultiplier(); if (number._result == 0) { return; } } s1.Stop(); // Version 2: use Lazy Value. var s2 = Stopwatch.StartNew(); var lazy = new Lazy<NumberMultiplier>(); for (int i = 0; i < _max; i++) { if (lazy.Value._result == 0) { return; } } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); } }1037.23 ns new NumberMultiplier 1.28 ns Lazy.Value
The most interesting part of types in .NET is their internal implementations. This is an efficient way to learn about making types.
InitValue
and CreateValue
typically lead to a call to Activator.CreateInstance
, which is a way to invoke the target constructor.In an overloaded constructor, you can specify thread safety, and even specify a Func
type that serves as a factory design pattern method.
Func
will be invoked when the Value property is referenced. It is invoked only if needed.Lazy represents a reliable lazy initialization capability. It can reduce startup time in cases where its overhead is dwarfed by the cost of the initialization.