1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 // --------------------------------------------------------------------------------------
7 // A class that provides a simple, lightweight implementation of lazy initialization,
8 // obviating the need for a developer to implement a custom, thread-safe lazy initialization
11 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
13 using System
.Diagnostics
;
14 using System
.Diagnostics
.CodeAnalysis
;
15 using System
.Runtime
.ExceptionServices
;
16 using System
.Threading
;
20 internal enum LazyState
22 NoneViaConstructor
= 0,
26 PublicationOnlyViaConstructor
= 3,
27 PublicationOnlyViaFactory
= 4,
28 PublicationOnlyWait
= 5,
29 PublicationOnlyException
= 6,
31 ExecutionAndPublicationViaConstructor
= 7,
32 ExecutionAndPublicationViaFactory
= 8,
33 ExecutionAndPublicationException
= 9,
37 /// LazyHelper serves multiples purposes
38 /// - minimizing code size of Lazy<T> by implementing as much of the code that is not generic
39 /// this reduces generic code bloat, making faster class initialization
40 /// - contains singleton objects that are used to handle threading primitives for PublicationOnly mode
41 /// - allows for instantiation for ExecutionAndPublication so as to create an object for locking on
42 /// - holds exception information.
44 internal class LazyHelper
46 internal static readonly LazyHelper NoneViaConstructor
= new LazyHelper(LazyState
.NoneViaConstructor
);
47 internal static readonly LazyHelper NoneViaFactory
= new LazyHelper(LazyState
.NoneViaFactory
);
48 internal static readonly LazyHelper PublicationOnlyViaConstructor
= new LazyHelper(LazyState
.PublicationOnlyViaConstructor
);
49 internal static readonly LazyHelper PublicationOnlyViaFactory
= new LazyHelper(LazyState
.PublicationOnlyViaFactory
);
50 internal static readonly LazyHelper PublicationOnlyWaitForOtherThreadToPublish
= new LazyHelper(LazyState
.PublicationOnlyWait
);
52 internal LazyState State { get; }
54 private readonly ExceptionDispatchInfo
? _exceptionDispatch
;
57 /// Constructor that defines the state
59 internal LazyHelper(LazyState state
)
65 /// Constructor used for exceptions
67 internal LazyHelper(LazyThreadSafetyMode mode
, Exception exception
)
71 case LazyThreadSafetyMode
.ExecutionAndPublication
:
72 State
= LazyState
.ExecutionAndPublicationException
;
75 case LazyThreadSafetyMode
.None
:
76 State
= LazyState
.NoneException
;
79 case LazyThreadSafetyMode
.PublicationOnly
:
80 State
= LazyState
.PublicationOnlyException
;
84 Debug
.Fail("internal constructor, this should never occur");
88 _exceptionDispatch
= ExceptionDispatchInfo
.Capture(exception
);
92 internal void ThrowException()
94 Debug
.Assert(_exceptionDispatch
!= null, "execution path is invalid");
96 _exceptionDispatch
.Throw();
99 private LazyThreadSafetyMode
GetMode()
103 case LazyState
.NoneViaConstructor
:
104 case LazyState
.NoneViaFactory
:
105 case LazyState
.NoneException
:
106 return LazyThreadSafetyMode
.None
;
108 case LazyState
.PublicationOnlyViaConstructor
:
109 case LazyState
.PublicationOnlyViaFactory
:
110 case LazyState
.PublicationOnlyWait
:
111 case LazyState
.PublicationOnlyException
:
112 return LazyThreadSafetyMode
.PublicationOnly
;
114 case LazyState
.ExecutionAndPublicationViaConstructor
:
115 case LazyState
.ExecutionAndPublicationViaFactory
:
116 case LazyState
.ExecutionAndPublicationException
:
117 return LazyThreadSafetyMode
.ExecutionAndPublication
;
120 Debug
.Fail("Invalid logic; State should always have a valid value");
125 internal static LazyThreadSafetyMode
? GetMode(LazyHelper
? state
)
128 return null; // we don't know the mode anymore
129 return state
.GetMode();
132 internal static bool GetIsValueFaulted(LazyHelper
? state
) => state
?._exceptionDispatch
!= null;
134 internal static LazyHelper
Create(LazyThreadSafetyMode mode
, bool useDefaultConstructor
)
138 case LazyThreadSafetyMode
.None
:
139 return useDefaultConstructor
? NoneViaConstructor
: NoneViaFactory
;
141 case LazyThreadSafetyMode
.PublicationOnly
:
142 return useDefaultConstructor
? PublicationOnlyViaConstructor
: PublicationOnlyViaFactory
;
144 case LazyThreadSafetyMode
.ExecutionAndPublication
:
145 // we need to create an object for ExecutionAndPublication because we use Monitor-based locking
146 LazyState state
= useDefaultConstructor
?
147 LazyState
.ExecutionAndPublicationViaConstructor
:
148 LazyState
.ExecutionAndPublicationViaFactory
;
149 return new LazyHelper(state
);
152 throw new ArgumentOutOfRangeException(nameof(mode
), SR
.Lazy_ctor_ModeInvalid
);
156 internal static T CreateViaDefaultConstructor
<T
>()
160 return Activator
.CreateInstance
<T
>();
162 catch (MissingMethodException
)
164 throw new MissingMemberException(SR
.Lazy_CreateValue_NoParameterlessCtorForT
);
168 internal static LazyThreadSafetyMode
GetModeFromIsThreadSafe(bool isThreadSafe
)
170 return isThreadSafe
? LazyThreadSafetyMode
.ExecutionAndPublication
: LazyThreadSafetyMode
.None
;
175 /// Provides support for lazy initialization.
177 /// <typeparam name="T">Specifies the type of element being lazily initialized.</typeparam>
180 /// By default, all public and protected members of <see cref="Lazy{T}"/> are thread-safe and may be used
181 /// concurrently from multiple threads. These thread-safety guarantees may be removed optionally and per instance
182 /// using parameters to the type's constructors.
185 [DebuggerTypeProxy(typeof(LazyDebugView
<>))]
186 [DebuggerDisplay("ThreadSafetyMode={Mode}, IsValueCreated={IsValueCreated}, IsValueFaulted={IsValueFaulted}, Value={ValueForDebugDisplay}")]
189 private static T
CreateViaDefaultConstructor() => LazyHelper
.CreateViaDefaultConstructor
<T
>();
191 // _state, a volatile reference, is set to null after _value has been set
192 private volatile LazyHelper
? _state
;
194 // we ensure that _factory when finished is set to null to allow garbage collector to clean up
195 // any referenced items
196 private Func
<T
>? _factory
;
198 // _value eventually stores the lazily created value. It is valid when _state = null.
199 private T _value
= default!;
202 /// Initializes a new instance of the <see cref="System.Lazy{T}"/> class that
203 /// uses <typeparamref name="T"/>'s default constructor for lazy initialization.
206 /// An instance created with this constructor may be used concurrently from multiple threads.
209 : this(null, LazyThreadSafetyMode
.ExecutionAndPublication
, useDefaultConstructor
: true)
214 /// Initializes a new instance of the <see cref="System.Lazy{T}"/> class that
215 /// uses a pre-initialized specified value.
218 /// An instance created with this constructor should be usable by multiple threads
227 /// Initializes a new instance of the <see cref="System.Lazy{T}"/> class that uses a
228 /// specified initialization function.
230 /// <param name="valueFactory">
231 /// The <see cref="System.Func{T}"/> invoked to produce the lazily-initialized value when it is
234 /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is a null
235 /// reference (Nothing in Visual Basic).</exception>
237 /// An instance created with this constructor may be used concurrently from multiple threads.
239 public Lazy(Func
<T
> valueFactory
)
240 : this(valueFactory
, LazyThreadSafetyMode
.ExecutionAndPublication
, useDefaultConstructor
: false)
245 /// Initializes a new instance of the <see cref="System.Lazy{T}"/>
246 /// class that uses <typeparamref name="T"/>'s default constructor and a specified thread-safety mode.
248 /// <param name="isThreadSafe">true if this instance should be usable by multiple threads concurrently; false if the instance will only be used by one thread at a time.
250 public Lazy(bool isThreadSafe
) :
251 this(null, LazyHelper
.GetModeFromIsThreadSafe(isThreadSafe
), useDefaultConstructor
: true)
256 /// Initializes a new instance of the <see cref="System.Lazy{T}"/>
257 /// class that uses <typeparamref name="T"/>'s default constructor and a specified thread-safety mode.
259 /// <param name="mode">The lazy thread-safety mode</param>
260 /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="mode"/> mode contains an invalid valuee</exception>
261 public Lazy(LazyThreadSafetyMode mode
) :
262 this(null, mode
, useDefaultConstructor
: true)
267 /// Initializes a new instance of the <see cref="System.Lazy{T}"/> class
268 /// that uses a specified initialization function and a specified thread-safety mode.
270 /// <param name="valueFactory">
271 /// The <see cref="System.Func{T}"/> invoked to produce the lazily-initialized value when it is needed.
273 /// <param name="isThreadSafe">true if this instance should be usable by multiple threads concurrently; false if the instance will only be used by one thread at a time.
275 /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is
276 /// a null reference (Nothing in Visual Basic).</exception>
277 public Lazy(Func
<T
> valueFactory
, bool isThreadSafe
) :
278 this(valueFactory
, LazyHelper
.GetModeFromIsThreadSafe(isThreadSafe
), useDefaultConstructor
: false)
283 /// Initializes a new instance of the <see cref="System.Lazy{T}"/> class
284 /// that uses a specified initialization function and a specified thread-safety mode.
286 /// <param name="valueFactory">
287 /// The <see cref="System.Func{T}"/> invoked to produce the lazily-initialized value when it is needed.
289 /// <param name="mode">The lazy thread-safety mode.</param>
290 /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is
291 /// a null reference (Nothing in Visual Basic).</exception>
292 /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="mode"/> mode contains an invalid value.</exception>
293 public Lazy(Func
<T
> valueFactory
, LazyThreadSafetyMode mode
)
294 : this(valueFactory
, mode
, useDefaultConstructor
: false)
298 private Lazy(Func
<T
>? valueFactory
, LazyThreadSafetyMode mode
, bool useDefaultConstructor
)
300 if (valueFactory
== null && !useDefaultConstructor
)
301 throw new ArgumentNullException(nameof(valueFactory
));
303 _factory
= valueFactory
;
304 _state
= LazyHelper
.Create(mode
, useDefaultConstructor
);
307 private void ViaConstructor()
309 _value
= CreateViaDefaultConstructor();
310 _state
= null; // volatile write, must occur after setting _value
313 private void ViaFactory(LazyThreadSafetyMode mode
)
317 Func
<T
>? factory
= _factory
;
319 throw new InvalidOperationException(SR
.Lazy_Value_RecursiveCallsToValue
);
323 _state
= null; // volatile write, must occur after setting _value
325 catch (Exception exception
)
327 _state
= new LazyHelper(mode
, exception
);
332 private void ExecutionAndPublication(LazyHelper executionAndPublication
, bool useDefaultConstructor
)
334 lock (executionAndPublication
)
336 // it's possible for multiple calls to have piled up behind the lock, so we need to check
337 // to see if the ExecutionAndPublication object is still the current implementation.
338 if (ReferenceEquals(_state
, executionAndPublication
))
340 if (useDefaultConstructor
)
346 ViaFactory(LazyThreadSafetyMode
.ExecutionAndPublication
);
352 private void PublicationOnly(LazyHelper publicationOnly
, T possibleValue
)
354 LazyHelper
? previous
= Interlocked
.CompareExchange(ref _state
, LazyHelper
.PublicationOnlyWaitForOtherThreadToPublish
, publicationOnly
);
355 if (previous
== publicationOnly
)
358 _value
= possibleValue
;
359 _state
= null; // volatile write, must occur after setting _value
363 private void PublicationOnlyViaConstructor(LazyHelper initializer
)
365 PublicationOnly(initializer
, CreateViaDefaultConstructor());
368 private void PublicationOnlyViaFactory(LazyHelper initializer
)
370 Func
<T
>? factory
= _factory
;
373 PublicationOnlyWaitForOtherThreadToPublish();
377 PublicationOnly(initializer
, factory());
381 private void PublicationOnlyWaitForOtherThreadToPublish()
383 var spinWait
= new SpinWait();
384 while (!ReferenceEquals(_state
, null))
386 // We get here when PublicationOnly temporarily sets _state to LazyHelper.PublicationOnlyWaitForOtherThreadToPublish.
387 // This temporary state should be quickly followed by _state being set to null.
392 private T
CreateValue()
394 // we have to create a copy of state here, and use the copy exclusively from here on in
395 // so as to ensure thread safety.
396 LazyHelper
? state
= _state
;
401 case LazyState
.NoneViaConstructor
:
405 case LazyState
.NoneViaFactory
:
406 ViaFactory(LazyThreadSafetyMode
.None
);
409 case LazyState
.PublicationOnlyViaConstructor
:
410 PublicationOnlyViaConstructor(state
);
413 case LazyState
.PublicationOnlyViaFactory
:
414 PublicationOnlyViaFactory(state
);
417 case LazyState
.PublicationOnlyWait
:
418 PublicationOnlyWaitForOtherThreadToPublish();
421 case LazyState
.ExecutionAndPublicationViaConstructor
:
422 ExecutionAndPublication(state
, useDefaultConstructor
: true);
425 case LazyState
.ExecutionAndPublicationViaFactory
:
426 ExecutionAndPublication(state
, useDefaultConstructor
: false);
430 state
.ThrowException();
437 /// <summary>Creates and returns a string representation of this instance.</summary>
438 /// <returns>The result of calling <see cref="object.ToString"/> on the <see
439 /// cref="Value"/>.</returns>
440 /// <exception cref="System.NullReferenceException">
441 /// The <see cref="Value"/> is null.
443 public override string? ToString()
445 return IsValueCreated
?
446 Value
!.ToString() : // Throws NullReferenceException as if caller called ToString on the value itself
447 SR
.Lazy_ToString_ValueNotCreated
;
450 /// <summary>Gets the value of the Lazy<T> for debugging display purposes.</summary>
452 internal T ValueForDebugDisplay
465 /// Gets a value indicating whether this instance may be used concurrently from multiple threads.
467 internal LazyThreadSafetyMode
? Mode
=> LazyHelper
.GetMode(_state
);
470 /// Gets whether the value creation is faulted or not
472 internal bool IsValueFaulted
=> LazyHelper
.GetIsValueFaulted(_state
);
474 /// <summary>Gets a value indicating whether the <see cref="System.Lazy{T}"/> has been initialized.
476 /// <value>true if the <see cref="System.Lazy{T}"/> instance has been initialized;
477 /// otherwise, false.</value>
479 /// The initialization of a <see cref="System.Lazy{T}"/> instance may result in either
480 /// a value being produced or an exception being thrown. If an exception goes unhandled during initialization,
481 /// <see cref="IsValueCreated"/> will return false.
483 public bool IsValueCreated
=> _state
== null;
485 /// <summary>Gets the lazily initialized value of the current <see
486 /// cref="System.Lazy{T}"/>.</summary>
487 /// <value>The lazily initialized value of the current <see
488 /// cref="System.Lazy{T}"/>.</value>
489 /// <exception cref="System.MissingMemberException">
490 /// The <see cref="System.Lazy{T}"/> was initialized to use the default constructor
491 /// of the type being lazily initialized, and that type does not have a public, parameterless constructor.
493 /// <exception cref="System.MemberAccessException">
494 /// The <see cref="System.Lazy{T}"/> was initialized to use the default constructor
495 /// of the type being lazily initialized, and permissions to access the constructor were missing.
497 /// <exception cref="System.InvalidOperationException">
498 /// The <see cref="System.Lazy{T}"/> was constructed with the <see cref="System.Threading.LazyThreadSafetyMode.ExecutionAndPublication"/> or
499 /// <see cref="System.Threading.LazyThreadSafetyMode.None"/> and the initialization function attempted to access <see cref="Value"/> on this instance.
502 /// If <see cref="IsValueCreated"/> is false, accessing <see cref="Value"/> will force initialization.
503 /// Please <see cref="System.Threading.LazyThreadSafetyMode"/> for more information on how <see cref="System.Lazy{T}"/> will behave if an exception is thrown
504 /// from initialization delegate.
506 [DebuggerBrowsable(DebuggerBrowsableState
.Never
)]
507 public T Value
=> _state
== null ? _value
: CreateValue();
510 /// <summary>A debugger view of the Lazy<T> to surface additional debugging properties and
511 /// to ensure that the Lazy<T> does not become initialized if it was not already.</summary>
512 internal sealed class LazyDebugView
<T
>
514 //The Lazy object being viewed.
515 private readonly Lazy
<T
> _lazy
;
517 /// <summary>Constructs a new debugger view object for the provided Lazy object.</summary>
518 /// <param name="lazy">A Lazy object to browse in the debugger.</param>
519 public LazyDebugView(Lazy
<T
> lazy
)
524 /// <summary>Returns whether the Lazy object is initialized or not.</summary>
525 public bool IsValueCreated
=> _lazy
.IsValueCreated
;
527 /// <summary>Returns the value of the Lazy object.</summary>
528 public T Value
=> _lazy
.ValueForDebugDisplay
;
530 /// <summary>Returns the execution mode of the Lazy object</summary>
531 public LazyThreadSafetyMode
? Mode
=> _lazy
.Mode
;
533 /// <summary>Returns the execution mode of the Lazy object</summary>
534 public bool IsValueFaulted
=> _lazy
.IsValueFaulted
;