// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // -------------------------------------------------------------------------------------- // // A class that provides a simple, lightweight implementation of lazy initialization, // obviating the need for a developer to implement a custom, thread-safe lazy initialization // solution. // // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- #pragma warning disable 0420 using System.Diagnostics; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Threading; namespace System { internal enum LazyState { NoneViaConstructor = 0, NoneViaFactory = 1, NoneException = 2, PublicationOnlyViaConstructor = 3, PublicationOnlyViaFactory = 4, PublicationOnlyWait = 5, PublicationOnlyException = 6, ExecutionAndPublicationViaConstructor = 7, ExecutionAndPublicationViaFactory = 8, ExecutionAndPublicationException = 9, } /// /// LazyHelper serves multiples purposes /// - minimizing code size of Lazy<T> by implementing as much of the code that is not generic /// this reduces generic code bloat, making faster class initialization /// - contains singleton objects that are used to handle threading primitives for PublicationOnly mode /// - allows for instantiation for ExecutionAndPublication so as to create an object for locking on /// - holds exception information. /// internal class LazyHelper { internal readonly static LazyHelper NoneViaConstructor = new LazyHelper(LazyState.NoneViaConstructor); internal readonly static LazyHelper NoneViaFactory = new LazyHelper(LazyState.NoneViaFactory); internal readonly static LazyHelper PublicationOnlyViaConstructor = new LazyHelper(LazyState.PublicationOnlyViaConstructor); internal readonly static LazyHelper PublicationOnlyViaFactory = new LazyHelper(LazyState.PublicationOnlyViaFactory); internal readonly static LazyHelper PublicationOnlyWaitForOtherThreadToPublish = new LazyHelper(LazyState.PublicationOnlyWait); internal LazyState State { get; } private readonly ExceptionDispatchInfo _exceptionDispatch; /// /// Constructor that defines the state /// internal LazyHelper(LazyState state) { State = state; } /// /// Constructor used for exceptions /// internal LazyHelper(LazyThreadSafetyMode mode, Exception exception) { switch(mode) { case LazyThreadSafetyMode.ExecutionAndPublication: State = LazyState.ExecutionAndPublicationException; break; case LazyThreadSafetyMode.None: State = LazyState.NoneException; break; case LazyThreadSafetyMode.PublicationOnly: State = LazyState.PublicationOnlyException; break; default: Debug.Fail("internal constructor, this should never occur"); break; } _exceptionDispatch = ExceptionDispatchInfo.Capture(exception); } internal void ThrowException() { Debug.Assert(_exceptionDispatch != null, "execution path is invalid"); _exceptionDispatch.Throw(); } private LazyThreadSafetyMode GetMode() { switch (State) { case LazyState.NoneViaConstructor: case LazyState.NoneViaFactory: case LazyState.NoneException: return LazyThreadSafetyMode.None; case LazyState.PublicationOnlyViaConstructor: case LazyState.PublicationOnlyViaFactory: case LazyState.PublicationOnlyWait: case LazyState.PublicationOnlyException: return LazyThreadSafetyMode.PublicationOnly; case LazyState.ExecutionAndPublicationViaConstructor: case LazyState.ExecutionAndPublicationViaFactory: case LazyState.ExecutionAndPublicationException: return LazyThreadSafetyMode.ExecutionAndPublication; default: Debug.Fail("Invalid logic; State should always have a valid value"); return default; } } internal static LazyThreadSafetyMode? GetMode(LazyHelper state) { if (state == null) return null; // we don't know the mode anymore return state.GetMode(); } internal static bool GetIsValueFaulted(LazyHelper state) => state?._exceptionDispatch != null; internal static LazyHelper Create(LazyThreadSafetyMode mode, bool useDefaultConstructor) { switch (mode) { case LazyThreadSafetyMode.None: return useDefaultConstructor ? NoneViaConstructor : NoneViaFactory; case LazyThreadSafetyMode.PublicationOnly: return useDefaultConstructor ? PublicationOnlyViaConstructor : PublicationOnlyViaFactory; case LazyThreadSafetyMode.ExecutionAndPublication: // we need to create an object for ExecutionAndPublication because we use Monitor-based locking var state = useDefaultConstructor ? LazyState.ExecutionAndPublicationViaConstructor : LazyState.ExecutionAndPublicationViaFactory; return new LazyHelper(state); default: throw new ArgumentOutOfRangeException(nameof(mode), SR.Lazy_ctor_ModeInvalid); } } internal static object CreateViaDefaultConstructor(Type type) { try { return Activator.CreateInstance(type); } catch (MissingMethodException) { throw new MissingMemberException(SR.Lazy_CreateValue_NoParameterlessCtorForT); } } internal static LazyThreadSafetyMode GetModeFromIsThreadSafe(bool isThreadSafe) { return isThreadSafe ? LazyThreadSafetyMode.ExecutionAndPublication : LazyThreadSafetyMode.None; } } /// /// Provides support for lazy initialization. /// /// Specifies the type of element being lazily initialized. /// /// /// By default, all public and protected members of are thread-safe and may be used /// concurrently from multiple threads. These thread-safety guarantees may be removed optionally and per instance /// using parameters to the type's constructors. /// /// [DebuggerTypeProxy(typeof(LazyDebugView<>))] [DebuggerDisplay("ThreadSafetyMode={Mode}, IsValueCreated={IsValueCreated}, IsValueFaulted={IsValueFaulted}, Value={ValueForDebugDisplay}")] public class Lazy { private static T CreateViaDefaultConstructor() { return (T)LazyHelper.CreateViaDefaultConstructor(typeof(T)); } // _state, a volatile reference, is set to null after _value has been set private volatile LazyHelper _state; // we ensure that _factory when finished is set to null to allow garbage collector to clean up // any referenced items private Func _factory; // _value eventually stores the lazily created value. It is valid when _state = null. private T _value; /// /// Initializes a new instance of the class that /// uses 's default constructor for lazy initialization. /// /// /// An instance created with this constructor may be used concurrently from multiple threads. /// public Lazy() : this(null, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor:true) { } /// /// Initializes a new instance of the class that /// uses a pre-initialized specified value. /// /// /// An instance created with this constructor should be usable by multiple threads /// concurrently. /// public Lazy(T value) { _value = value; } /// /// Initializes a new instance of the class that uses a /// specified initialization function. /// /// /// The invoked to produce the lazily-initialized value when it is /// needed. /// /// is a null /// reference (Nothing in Visual Basic). /// /// An instance created with this constructor may be used concurrently from multiple threads. /// public Lazy(Func valueFactory) : this(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor:false) { } /// /// Initializes a new instance of the /// class that uses 's default constructor and a specified thread-safety mode. /// /// 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. /// public Lazy(bool isThreadSafe) : this(null, LazyHelper.GetModeFromIsThreadSafe(isThreadSafe), useDefaultConstructor:true) { } /// /// Initializes a new instance of the /// class that uses 's default constructor and a specified thread-safety mode. /// /// The lazy thread-safety mode /// mode contains an invalid valuee public Lazy(LazyThreadSafetyMode mode) : this(null, mode, useDefaultConstructor:true) { } /// /// Initializes a new instance of the class /// that uses a specified initialization function and a specified thread-safety mode. /// /// /// The invoked to produce the lazily-initialized value when it is needed. /// /// 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. /// /// is /// a null reference (Nothing in Visual Basic). public Lazy(Func valueFactory, bool isThreadSafe) : this(valueFactory, LazyHelper.GetModeFromIsThreadSafe(isThreadSafe), useDefaultConstructor:false) { } /// /// Initializes a new instance of the class /// that uses a specified initialization function and a specified thread-safety mode. /// /// /// The invoked to produce the lazily-initialized value when it is needed. /// /// The lazy thread-safety mode. /// is /// a null reference (Nothing in Visual Basic). /// mode contains an invalid value. public Lazy(Func valueFactory, LazyThreadSafetyMode mode) : this(valueFactory, mode, useDefaultConstructor:false) { } private Lazy(Func valueFactory, LazyThreadSafetyMode mode, bool useDefaultConstructor) { if (valueFactory == null && !useDefaultConstructor) throw new ArgumentNullException(nameof(valueFactory)); _factory = valueFactory; _state = LazyHelper.Create(mode, useDefaultConstructor); } private void ViaConstructor() { _value = CreateViaDefaultConstructor(); _state = null; // volatile write, must occur after setting _value } private void ViaFactory(LazyThreadSafetyMode mode) { try { Func factory = _factory; if (factory == null) throw new InvalidOperationException(SR.Lazy_Value_RecursiveCallsToValue); _factory = null; _value = factory(); _state = null; // volatile write, must occur after setting _value } catch (Exception exception) { _state = new LazyHelper(mode, exception); throw; } } private void ExecutionAndPublication(LazyHelper executionAndPublication, bool useDefaultConstructor) { lock (executionAndPublication) { // it's possible for multiple calls to have piled up behind the lock, so we need to check // to see if the ExecutionAndPublication object is still the current implementation. if (ReferenceEquals(_state, executionAndPublication)) { if (useDefaultConstructor) { ViaConstructor(); } else { ViaFactory(LazyThreadSafetyMode.ExecutionAndPublication); } } } } private void PublicationOnly(LazyHelper publicationOnly, T possibleValue) { LazyHelper previous = Interlocked.CompareExchange(ref _state, LazyHelper.PublicationOnlyWaitForOtherThreadToPublish, publicationOnly); if (previous == publicationOnly) { _factory = null; _value = possibleValue; _state = null; // volatile write, must occur after setting _value } } private void PublicationOnlyViaConstructor(LazyHelper initializer) { PublicationOnly(initializer, CreateViaDefaultConstructor()); } private void PublicationOnlyViaFactory(LazyHelper initializer) { Func factory = _factory; if (factory == null) { PublicationOnlyWaitForOtherThreadToPublish(); } else { PublicationOnly(initializer, factory()); } } private void PublicationOnlyWaitForOtherThreadToPublish() { var spinWait = new SpinWait(); while (!ReferenceEquals(_state, null)) { // We get here when PublicationOnly temporarily sets _state to LazyHelper.PublicationOnlyWaitForOtherThreadToPublish. // This temporary state should be quickly followed by _state being set to null. spinWait.SpinOnce(); } } private T CreateValue() { // we have to create a copy of state here, and use the copy exclusively from here on in // so as to ensure thread safety. var state = _state; if (state != null) { switch (state.State) { case LazyState.NoneViaConstructor: ViaConstructor(); break; case LazyState.NoneViaFactory: ViaFactory(LazyThreadSafetyMode.None); break; case LazyState.PublicationOnlyViaConstructor: PublicationOnlyViaConstructor(state); break; case LazyState.PublicationOnlyViaFactory: PublicationOnlyViaFactory(state); break; case LazyState.PublicationOnlyWait: PublicationOnlyWaitForOtherThreadToPublish(); break; case LazyState.ExecutionAndPublicationViaConstructor: ExecutionAndPublication(state, useDefaultConstructor:true); break; case LazyState.ExecutionAndPublicationViaFactory: ExecutionAndPublication(state, useDefaultConstructor:false); break; default: state.ThrowException(); break; } } return Value; } /// Creates and returns a string representation of this instance. /// The result of calling on the . /// /// The is null. /// public override string ToString() { return IsValueCreated ? Value.ToString() : SR.Lazy_ToString_ValueNotCreated; } /// Gets the value of the Lazy<T> for debugging display purposes. internal T ValueForDebugDisplay { get { if (!IsValueCreated) { return default; } return _value; } } /// /// Gets a value indicating whether this instance may be used concurrently from multiple threads. /// internal LazyThreadSafetyMode? Mode => LazyHelper.GetMode(_state); /// /// Gets whether the value creation is faulted or not /// internal bool IsValueFaulted => LazyHelper.GetIsValueFaulted(_state); /// Gets a value indicating whether the has been initialized. /// /// true if the instance has been initialized; /// otherwise, false. /// /// The initialization of a instance may result in either /// a value being produced or an exception being thrown. If an exception goes unhandled during initialization, /// will return false. /// public bool IsValueCreated => _state == null; /// Gets the lazily initialized value of the current . /// The lazily initialized value of the current . /// /// The was initialized to use the default constructor /// of the type being lazily initialized, and that type does not have a public, parameterless constructor. /// /// /// The was initialized to use the default constructor /// of the type being lazily initialized, and permissions to access the constructor were missing. /// /// /// The was constructed with the or /// and the initialization function attempted to access on this instance. /// /// /// If is false, accessing will force initialization. /// Please for more information on how will behave if an exception is thrown /// from initialization delegate. /// [DebuggerBrowsable(DebuggerBrowsableState.Never)] public T Value => _state == null ? _value : CreateValue(); } /// A debugger view of the Lazy<T> to surface additional debugging properties and /// to ensure that the Lazy<T> does not become initialized if it was not already. internal sealed class LazyDebugView { //The Lazy object being viewed. private readonly Lazy _lazy; /// Constructs a new debugger view object for the provided Lazy object. /// A Lazy object to browse in the debugger. public LazyDebugView(Lazy lazy) { _lazy = lazy; } /// Returns whether the Lazy object is initialized or not. public bool IsValueCreated { get { return _lazy.IsValueCreated; } } /// Returns the value of the Lazy object. public T Value { get { return _lazy.ValueForDebugDisplay; } } /// Returns the execution mode of the Lazy object public LazyThreadSafetyMode? Mode { get { return _lazy.Mode; } } /// Returns the execution mode of the Lazy object public bool IsValueFaulted { get { return _lazy.IsValueFaulted; } } } }