|
// 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;
}
}
|