Fix StyleCop warning SA1024 (colon spacing)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Lazy.cs
blobfb13974d28060ef7f82fd5a457ee9795083058a3
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 // --------------------------------------------------------------------------------------
6 //
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
9 // solution.
11 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
13 using System.Diagnostics;
14 using System.Diagnostics.CodeAnalysis;
15 using System.Runtime.ExceptionServices;
16 using System.Threading;
18 namespace System
20 internal enum LazyState
22 NoneViaConstructor = 0,
23 NoneViaFactory = 1,
24 NoneException = 2,
26 PublicationOnlyViaConstructor = 3,
27 PublicationOnlyViaFactory = 4,
28 PublicationOnlyWait = 5,
29 PublicationOnlyException = 6,
31 ExecutionAndPublicationViaConstructor = 7,
32 ExecutionAndPublicationViaFactory = 8,
33 ExecutionAndPublicationException = 9,
36 /// <summary>
37 /// LazyHelper serves multiples purposes
38 /// - minimizing code size of Lazy&lt;T&gt; 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.
43 /// </summary>
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;
56 /// <summary>
57 /// Constructor that defines the state
58 /// </summary>
59 internal LazyHelper(LazyState state)
61 State = state;
64 /// <summary>
65 /// Constructor used for exceptions
66 /// </summary>
67 internal LazyHelper(LazyThreadSafetyMode mode, Exception exception)
69 switch (mode)
71 case LazyThreadSafetyMode.ExecutionAndPublication:
72 State = LazyState.ExecutionAndPublicationException;
73 break;
75 case LazyThreadSafetyMode.None:
76 State = LazyState.NoneException;
77 break;
79 case LazyThreadSafetyMode.PublicationOnly:
80 State = LazyState.PublicationOnlyException;
81 break;
83 default:
84 Debug.Fail("internal constructor, this should never occur");
85 break;
88 _exceptionDispatch = ExceptionDispatchInfo.Capture(exception);
91 [DoesNotReturn]
92 internal void ThrowException()
94 Debug.Assert(_exceptionDispatch != null, "execution path is invalid");
96 _exceptionDispatch.Throw();
99 private LazyThreadSafetyMode GetMode()
101 switch (State)
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;
119 default:
120 Debug.Fail("Invalid logic; State should always have a valid value");
121 return default;
125 internal static LazyThreadSafetyMode? GetMode(LazyHelper? state)
127 if (state == null)
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)
136 switch (mode)
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);
151 default:
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;
174 /// <summary>
175 /// Provides support for lazy initialization.
176 /// </summary>
177 /// <typeparam name="T">Specifies the type of element being lazily initialized.</typeparam>
178 /// <remarks>
179 /// <para>
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.
183 /// </para>
184 /// </remarks>
185 [DebuggerTypeProxy(typeof(LazyDebugView<>))]
186 [DebuggerDisplay("ThreadSafetyMode={Mode}, IsValueCreated={IsValueCreated}, IsValueFaulted={IsValueFaulted}, Value={ValueForDebugDisplay}")]
187 public class Lazy<T>
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!;
201 /// <summary>
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.
204 /// </summary>
205 /// <remarks>
206 /// An instance created with this constructor may be used concurrently from multiple threads.
207 /// </remarks>
208 public Lazy()
209 : this(null, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor: true)
213 /// <summary>
214 /// Initializes a new instance of the <see cref="System.Lazy{T}"/> class that
215 /// uses a pre-initialized specified value.
216 /// </summary>
217 /// <remarks>
218 /// An instance created with this constructor should be usable by multiple threads
219 /// concurrently.
220 /// </remarks>
221 public Lazy(T value)
223 _value = value;
226 /// <summary>
227 /// Initializes a new instance of the <see cref="System.Lazy{T}"/> class that uses a
228 /// specified initialization function.
229 /// </summary>
230 /// <param name="valueFactory">
231 /// The <see cref="System.Func{T}"/> invoked to produce the lazily-initialized value when it is
232 /// needed.
233 /// </param>
234 /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is a null
235 /// reference (Nothing in Visual Basic).</exception>
236 /// <remarks>
237 /// An instance created with this constructor may be used concurrently from multiple threads.
238 /// </remarks>
239 public Lazy(Func<T> valueFactory)
240 : this(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor: false)
244 /// <summary>
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.
247 /// </summary>
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.
249 /// </param>
250 public Lazy(bool isThreadSafe) :
251 this(null, LazyHelper.GetModeFromIsThreadSafe(isThreadSafe), useDefaultConstructor: true)
255 /// <summary>
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.
258 /// </summary>
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)
266 /// <summary>
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.
269 /// </summary>
270 /// <param name="valueFactory">
271 /// The <see cref="System.Func{T}"/> invoked to produce the lazily-initialized value when it is needed.
272 /// </param>
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.
274 /// </param>
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)
282 /// <summary>
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.
285 /// </summary>
286 /// <param name="valueFactory">
287 /// The <see cref="System.Func{T}"/> invoked to produce the lazily-initialized value when it is needed.
288 /// </param>
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;
318 if (factory == null)
319 throw new InvalidOperationException(SR.Lazy_Value_RecursiveCallsToValue);
320 _factory = null;
322 _value = factory();
323 _state = null; // volatile write, must occur after setting _value
325 catch (Exception exception)
327 _state = new LazyHelper(mode, exception);
328 throw;
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)
342 ViaConstructor();
344 else
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)
357 _factory = null;
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;
371 if (factory == null)
373 PublicationOnlyWaitForOtherThreadToPublish();
375 else
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.
388 spinWait.SpinOnce();
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;
397 if (state != null)
399 switch (state.State)
401 case LazyState.NoneViaConstructor:
402 ViaConstructor();
403 break;
405 case LazyState.NoneViaFactory:
406 ViaFactory(LazyThreadSafetyMode.None);
407 break;
409 case LazyState.PublicationOnlyViaConstructor:
410 PublicationOnlyViaConstructor(state);
411 break;
413 case LazyState.PublicationOnlyViaFactory:
414 PublicationOnlyViaFactory(state);
415 break;
417 case LazyState.PublicationOnlyWait:
418 PublicationOnlyWaitForOtherThreadToPublish();
419 break;
421 case LazyState.ExecutionAndPublicationViaConstructor:
422 ExecutionAndPublication(state, useDefaultConstructor: true);
423 break;
425 case LazyState.ExecutionAndPublicationViaFactory:
426 ExecutionAndPublication(state, useDefaultConstructor: false);
427 break;
429 default:
430 state.ThrowException();
431 break;
434 return Value;
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.
442 /// </exception>
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&lt;T&gt; for debugging display purposes.</summary>
451 [MaybeNull]
452 internal T ValueForDebugDisplay
456 if (!IsValueCreated)
458 return default!;
460 return _value;
464 /// <summary>
465 /// Gets a value indicating whether this instance may be used concurrently from multiple threads.
466 /// </summary>
467 internal LazyThreadSafetyMode? Mode => LazyHelper.GetMode(_state);
469 /// <summary>
470 /// Gets whether the value creation is faulted or not
471 /// </summary>
472 internal bool IsValueFaulted => LazyHelper.GetIsValueFaulted(_state);
474 /// <summary>Gets a value indicating whether the <see cref="System.Lazy{T}"/> has been initialized.
475 /// </summary>
476 /// <value>true if the <see cref="System.Lazy{T}"/> instance has been initialized;
477 /// otherwise, false.</value>
478 /// <remarks>
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.
482 /// </remarks>
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.
492 /// </exception>
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.
496 /// </exception>
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.
500 /// </exception>
501 /// <remarks>
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.
505 /// </remarks>
506 [DebuggerBrowsable(DebuggerBrowsableState.Never)]
507 public T Value => _state == null ? _value : CreateValue();
510 /// <summary>A debugger view of the Lazy&lt;T&gt; to surface additional debugging properties and
511 /// to ensure that the Lazy&lt;T&gt; 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)
521 _lazy = 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;