// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ // // a set of lightweight static helpers for lazy initialization. // // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace System.Threading { /// <summary> /// Provides lazy initialization routines. /// </summary> /// <remarks> /// These routines avoid needing to allocate a dedicated, lazy-initialization instance, instead using /// references to ensure targets have been initialized as they are accessed. /// </remarks> public static class LazyInitializer { /// <summary> /// Initializes a target reference type with the type's default constructor if the target has not /// already been initialized. /// </summary> /// <typeparam name="T">The reference type of the reference to be initialized.</typeparam> /// <param name="target">A reference of type <typeparamref name="T"/> to initialize if it has not /// already been initialized.</param> /// <returns>The initialized reference of type <typeparamref name="T"/>.</returns> /// <exception cref="MissingMemberException">Type <typeparamref name="T"/> does not have a default /// constructor.</exception> /// <exception cref="MemberAccessException"> /// Permissions to access the constructor of type <typeparamref name="T"/> were missing. /// </exception> /// <remarks> /// <para> /// This method may only be used on reference types. To ensure initialization of value /// types, see other overloads of EnsureInitialized. /// </para> /// <para> /// This method may be used concurrently by multiple threads to initialize <paramref name="target"/>. /// In the event that multiple threads access this method concurrently, multiple instances of <typeparamref name="T"/> /// may be created, but only one will be stored into <paramref name="target"/>. In such an occurrence, this method will not dispose of the /// objects that were not stored. If such objects must be disposed, it is up to the caller to determine /// if an object was not used and to then dispose of the object appropriately. /// </para> /// </remarks> public static T EnsureInitialized<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>([NotNull] ref T? target) where T : class => Volatile.Read(ref target!) ?? EnsureInitializedCore(ref target); /// <summary> /// Initializes a target reference type with the type's default constructor (slow path) /// </summary> /// <typeparam name="T">The reference type of the reference to be initialized.</typeparam> /// <param name="target">The variable that need to be initialized</param> /// <returns>The initialized variable</returns> private static T EnsureInitializedCore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>([NotNull] ref T? target) where T : class { try { Interlocked.CompareExchange(ref target, Activator.CreateInstance<T>(), null!); } catch (MissingMethodException) { throw new MissingMemberException(SR.Lazy_CreateValue_NoParameterlessCtorForT); } Debug.Assert(target != null); return target; } /// <summary> /// Initializes a target reference type using the specified function if it has not already been /// initialized. /// </summary> /// <typeparam name="T">The reference type of the reference to be initialized.</typeparam> /// <param name="target">The reference of type <typeparamref name="T"/> to initialize if it has not /// already been initialized.</param> /// <param name="valueFactory">The <see cref="Func{T}"/> invoked to initialize the /// reference.</param> /// <returns>The initialized reference of type <typeparamref name="T"/>.</returns> /// <exception cref="MissingMemberException">Type <typeparamref name="T"/> does not have a /// default constructor.</exception> /// <exception cref="InvalidOperationException"><paramref name="valueFactory"/> returned /// null.</exception> /// <remarks> /// <para> /// This method may only be used on reference types, and <paramref name="valueFactory"/> may /// not return a null reference (<see langword="Nothing" /> in Visual Basic). To ensure initialization of value types or /// to allow null reference types, see other overloads of EnsureInitialized. /// </para> /// <para> /// This method may be used concurrently by multiple threads to initialize <paramref name="target"/>. /// In the event that multiple threads access this method concurrently, multiple instances of <typeparamref name="T"/> /// may be created, but only one will be stored into <paramref name="target"/>. In such an occurrence, this method will not dispose of the /// objects that were not stored. If such objects must be disposed, it is up to the caller to determine /// if an object was not used and to then dispose of the object appropriately. /// </para> /// </remarks> public static T EnsureInitialized<T>([NotNull] ref T? target, Func<T> valueFactory) where T : class => Volatile.Read(ref target!) ?? EnsureInitializedCore(ref target, valueFactory); /// <summary> /// Initialize the target using the given delegate (slow path). /// </summary> /// <typeparam name="T">The reference type of the reference to be initialized.</typeparam> /// <param name="target">The variable that need to be initialized</param> /// <param name="valueFactory">The delegate that will be executed to initialize the target</param> /// <returns>The initialized variable</returns> private static T EnsureInitializedCore<T>([NotNull] ref T? target, Func<T> valueFactory) where T : class { T value = valueFactory() ?? throw new InvalidOperationException(SR.Lazy_StaticInit_InvalidOperation); Interlocked.CompareExchange(ref target, value, null!); Debug.Assert(target != null); return target; } /// <summary> /// Initializes a target reference or value type with its default constructor if it has not already /// been initialized. /// </summary> /// <typeparam name="T">The type of the reference to be initialized.</typeparam> /// <param name="target">A reference or value of type <typeparamref name="T"/> to initialize if it /// has not already been initialized.</param> /// <param name="initialized">A reference to a boolean that determines whether the target has already /// been initialized.</param> /// <param name="syncLock">A reference to an object used as the mutually exclusive lock for initializing /// <paramref name="target"/>. If <paramref name="syncLock"/> is null, and if the target hasn't already /// been initialized, a new object will be instantiated.</param> /// <returns>The initialized value of type <typeparamref name="T"/>.</returns> public static T EnsureInitialized<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>([AllowNull] ref T target, ref bool initialized, [NotNullIfNotNull(nameof(syncLock))] ref object? syncLock) { // Fast path. if (Volatile.Read(ref initialized)) { return target!; } return EnsureInitializedCore<T>(ref target, ref initialized, ref syncLock); } /// <summary> /// Ensure the target is initialized and return the value (slow path). This overload permits nulls /// and also works for value type targets. Uses the type's default constructor to create the value. /// </summary> /// <typeparam name="T">The type of target.</typeparam> /// <param name="target">A reference to the target to be initialized.</param> /// <param name="initialized">A reference to a location tracking whether the target has been initialized.</param> /// <param name="syncLock">A reference to a location containing a mutual exclusive lock. If <paramref name="syncLock"/> is null, /// a new object will be instantiated. /// </param> /// <returns>The initialized object.</returns> private static T EnsureInitializedCore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>([AllowNull] ref T target, ref bool initialized, [NotNull] ref object? syncLock) { // Lazily initialize the lock if necessary and then double check if initialization is still required. lock (EnsureLockInitialized(ref syncLock)) { if (!Volatile.Read(ref initialized)) { try { target = Activator.CreateInstance<T>(); } catch (MissingMethodException) { throw new MissingMemberException(SR.Lazy_CreateValue_NoParameterlessCtorForT); } Volatile.Write(ref initialized, true); } } return target!; } /// <summary> /// Initializes a target reference or value type with a specified function if it has not already been /// initialized. /// </summary> /// <typeparam name="T">The type of the reference to be initialized.</typeparam> /// <param name="target">A reference or value of type <typeparamref name="T"/> to initialize if it /// has not already been initialized.</param> /// <param name="initialized">A reference to a boolean that determines whether the target has already /// been initialized.</param> /// <param name="syncLock">A reference to an object used as the mutually exclusive lock for initializing /// <paramref name="target"/>. If <paramref name="syncLock"/> is null, and if the target hasn't already /// been initialized, a new object will be instantiated.</param> /// <param name="valueFactory">The <see cref="Func{T}"/> invoked to initialize the /// reference or value.</param> /// <returns>The initialized value of type <typeparamref name="T"/>.</returns> public static T EnsureInitialized<T>([AllowNull] ref T target, ref bool initialized, [NotNullIfNotNull(nameof(syncLock))] ref object? syncLock, Func<T> valueFactory) { // Fast path. if (Volatile.Read(ref initialized)) { return target!; } return EnsureInitializedCore(ref target, ref initialized, ref syncLock, valueFactory); } /// <summary> /// Ensure the target is initialized and return the value (slow path). This overload permits nulls /// and also works for value type targets. Uses the supplied function to create the value. /// </summary> /// <typeparam name="T">The type of target.</typeparam> /// <param name="target">A reference to the target to be initialized.</param> /// <param name="initialized">A reference to a location tracking whether the target has been initialized.</param> /// <param name="syncLock">A reference to a location containing a mutual exclusive lock. If <paramref name="syncLock"/> is null, /// a new object will be instantiated.</param> /// <param name="valueFactory"> /// The <see cref="Func{T}"/> to invoke in order to produce the lazily-initialized value. /// </param> /// <returns>The initialized object.</returns> private static T EnsureInitializedCore<T>([AllowNull] ref T target, ref bool initialized, [NotNull] ref object? syncLock, Func<T> valueFactory) { // Lazily initialize the lock if necessary and then double check if initialization is still required. lock (EnsureLockInitialized(ref syncLock)) { if (!Volatile.Read(ref initialized)) { target = valueFactory(); Volatile.Write(ref initialized, true); } } return target!; } /// <summary> /// Initializes a target reference type with a specified function if it has not already been initialized. /// </summary> /// <typeparam name="T">The type of the reference to be initialized. Has to be reference type.</typeparam> /// <param name="target">A reference of type <typeparamref name="T"/> to initialize if it has not already been initialized.</param> /// <param name="syncLock">A reference to an object used as the mutually exclusive lock for initializing /// <paramref name="target"/>. If <paramref name="syncLock"/> is null, and if the target hasn't already /// been initialized, a new object will be instantiated.</param> /// <param name="valueFactory">The <see cref="Func{T}"/> invoked to initialize the reference.</param> /// <returns>The initialized value of type <typeparamref name="T"/>.</returns> public static T EnsureInitialized<T>([NotNull] ref T? target, [NotNullIfNotNull(nameof(syncLock))] ref object? syncLock, Func<T> valueFactory) where T : class => Volatile.Read(ref target!) ?? EnsureInitializedCore(ref target, ref syncLock, valueFactory); /// <summary> /// Ensure the target is initialized and return the value (slow path). This overload works only for reference type targets. /// Uses the supplied function to create the value. /// </summary> /// <typeparam name="T">The type of target. Has to be reference type.</typeparam> /// <param name="target">A reference to the target to be initialized.</param> /// <param name="syncLock">A reference to a location containing a mutual exclusive lock. If <paramref name="syncLock"/> is null, /// a new object will be instantiated.</param> /// <param name="valueFactory"> /// The <see cref="Func{T}"/> to invoke in order to produce the lazily-initialized value. /// </param> /// <returns>The initialized object.</returns> private static T EnsureInitializedCore<T>([NotNull] ref T? target, [NotNull] ref object? syncLock, Func<T> valueFactory) where T : class { // Lazily initialize the lock if necessary and then double check if initialization is still required. lock (EnsureLockInitialized(ref syncLock)) { if (Volatile.Read(ref target) == null) { Volatile.Write(ref target, valueFactory()); if (target == null) { throw new InvalidOperationException(SR.Lazy_StaticInit_InvalidOperation); } } } Debug.Assert(target != null); return target; } /// <summary> /// Ensure the lock object is initialized. /// </summary> /// <param name="syncLock">A reference to a location containing a mutual exclusive lock. If <paramref name="syncLock"/> is null, /// a new object will be instantiated.</param> /// <returns>Initialized lock object.</returns> private static object EnsureLockInitialized([NotNull] ref object? syncLock) => syncLock ?? Interlocked.CompareExchange(ref syncLock, new object(), null) ?? syncLock; } }