536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
508 lines
21 KiB
C#
508 lines
21 KiB
C#
#pragma warning disable 0420
|
|
// ==++==
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// ==--==
|
|
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
|
//
|
|
// Lazy.cs
|
|
//
|
|
// <OWNER>Microsoft</OWNER>
|
|
//
|
|
// --------------------------------------------------------------------------------------
|
|
//
|
|
// 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.
|
|
//
|
|
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
|
|
|
using System.Runtime;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security;
|
|
using System.Security.Permissions;
|
|
using System.Diagnostics;
|
|
using System.Runtime.Serialization;
|
|
using System.Threading;
|
|
using System.Diagnostics.Contracts;
|
|
using System.Runtime.ExceptionServices;
|
|
|
|
namespace System
|
|
{
|
|
// Lazy<T> is generic, but not all of its state needs to be generic. Avoid creating duplicate
|
|
// objects per instantiation by putting them here.
|
|
internal static class LazyHelpers
|
|
{
|
|
// Dummy object used as the value of m_threadSafeObj if in PublicationOnly mode.
|
|
internal static readonly object PUBLICATION_ONLY_SENTINEL = new object();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides support for lazy initialization.
|
|
/// </summary>
|
|
/// <typeparam name="T">Specifies the type of element being lazily initialized.</typeparam>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// By default, all public and protected members of <see cref="Lazy{T}"/> 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.
|
|
/// </para>
|
|
/// </remarks>
|
|
[Serializable]
|
|
[ComVisible(false)]
|
|
#if !FEATURE_CORECLR
|
|
[HostProtection(Synchronization = true, ExternalThreading = true)]
|
|
#endif
|
|
[DebuggerTypeProxy(typeof(System_LazyDebugView<>))]
|
|
[DebuggerDisplay("ThreadSafetyMode={Mode}, IsValueCreated={IsValueCreated}, IsValueFaulted={IsValueFaulted}, Value={ValueForDebugDisplay}")]
|
|
public class Lazy<T>
|
|
{
|
|
|
|
#region Inner classes
|
|
/// <summary>
|
|
/// wrapper class to box the initialized value, this is mainly created to avoid boxing/unboxing the value each time the value is called in case T is
|
|
/// a value type
|
|
/// </summary>
|
|
[Serializable]
|
|
class Boxed
|
|
{
|
|
internal Boxed(T value)
|
|
{
|
|
m_value = value;
|
|
}
|
|
internal T m_value;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Wrapper class to wrap the excpetion thrown by the value factory
|
|
/// </summary>
|
|
class LazyInternalExceptionHolder
|
|
{
|
|
internal ExceptionDispatchInfo m_edi;
|
|
internal LazyInternalExceptionHolder(Exception ex)
|
|
{
|
|
m_edi = ExceptionDispatchInfo.Capture(ex);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
// A dummy delegate used as a :
|
|
// 1- Flag to avoid recursive call to Value in None and ExecutionAndPublication modes in m_valueFactory
|
|
// 2- Flag to m_threadSafeObj if ExecutionAndPublication mode and the value is known to be initialized
|
|
static readonly Func<T> ALREADY_INVOKED_SENTINEL = delegate
|
|
{
|
|
Contract.Assert(false, "ALREADY_INVOKED_SENTINEL should never be invoked.");
|
|
return default(T);
|
|
};
|
|
|
|
//null --> value is not created
|
|
//m_value is Boxed --> the value is created, and m_value holds the value
|
|
//m_value is LazyExceptionHolder --> it holds an exception
|
|
private object m_boxed;
|
|
|
|
// The factory delegate that returns the value.
|
|
// In None and ExecutionAndPublication modes, this will be set to ALREADY_INVOKED_SENTINEL as a flag to avoid recursive calls
|
|
[NonSerialized]
|
|
private Func<T> m_valueFactory;
|
|
|
|
// null if it is not thread safe mode
|
|
// LazyHelpers.PUBLICATION_ONLY_SENTINEL if PublicationOnly mode
|
|
// object if ExecutionAndPublication mode (may be ALREADY_INVOKED_SENTINEL if the value is already initialized)
|
|
[NonSerialized]
|
|
private object m_threadSafeObj;
|
|
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class that
|
|
/// uses <typeparamref name="T"/>'s default constructor for lazy initialization.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// An instance created with this constructor may be used concurrently from multiple threads.
|
|
/// </remarks>
|
|
public Lazy()
|
|
: this(LazyThreadSafetyMode.ExecutionAndPublication)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class that uses a
|
|
/// specified initialization function.
|
|
/// </summary>
|
|
/// <param name="valueFactory">
|
|
/// The <see cref="T:System.Func{T}"/> invoked to produce the lazily-initialized value when it is
|
|
/// needed.
|
|
/// </param>
|
|
/// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is a null
|
|
/// reference (Nothing in Visual Basic).</exception>
|
|
/// <remarks>
|
|
/// An instance created with this constructor may be used concurrently from multiple threads.
|
|
/// </remarks>
|
|
public Lazy(Func<T> valueFactory)
|
|
: this(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/>
|
|
/// class that uses <typeparamref name="T"/>'s default constructor and a specified thread-safety mode.
|
|
/// </summary>
|
|
/// <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.
|
|
/// </param>
|
|
public Lazy(bool isThreadSafe) :
|
|
this(isThreadSafe? LazyThreadSafetyMode.ExecutionAndPublication : LazyThreadSafetyMode.None)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/>
|
|
/// class that uses <typeparamref name="T"/>'s default constructor and a specified thread-safety mode.
|
|
/// </summary>
|
|
/// <param name="mode">The lazy thread-safety mode mode</param>
|
|
/// <exception cref="System.ArgumentOutOfRangeException"><paramref name="mode"/> mode contains an invalid valuee</exception>
|
|
public Lazy(LazyThreadSafetyMode mode)
|
|
{
|
|
m_threadSafeObj = GetObjectFromMode(mode);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class
|
|
/// that uses a specified initialization function and a specified thread-safety mode.
|
|
/// </summary>
|
|
/// <param name="valueFactory">
|
|
/// The <see cref="T:System.Func{T}"/> invoked to produce the lazily-initialized value when it is needed.
|
|
/// </param>
|
|
/// <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.
|
|
/// </param>
|
|
/// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is
|
|
/// a null reference (Nothing in Visual Basic).</exception>
|
|
public Lazy(Func<T> valueFactory, bool isThreadSafe)
|
|
: this(valueFactory, isThreadSafe ? LazyThreadSafetyMode.ExecutionAndPublication : LazyThreadSafetyMode.None)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class
|
|
/// that uses a specified initialization function and a specified thread-safety mode.
|
|
/// </summary>
|
|
/// <param name="valueFactory">
|
|
/// The <see cref="T:System.Func{T}"/> invoked to produce the lazily-initialized value when it is needed.
|
|
/// </param>
|
|
/// <param name="mode">The lazy thread-safety mode.</param>
|
|
/// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is
|
|
/// a null reference (Nothing in Visual Basic).</exception>
|
|
/// <exception cref="System.ArgumentOutOfRangeException"><paramref name="mode"/> mode contains an invalid value.</exception>
|
|
public Lazy(Func<T> valueFactory, LazyThreadSafetyMode mode)
|
|
{
|
|
if (valueFactory == null)
|
|
throw new ArgumentNullException("valueFactory");
|
|
|
|
m_threadSafeObj = GetObjectFromMode(mode);
|
|
m_valueFactory = valueFactory;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Static helper function that returns an object based on the given mode. it also throws an exception if the mode is invalid
|
|
/// </summary>
|
|
private static object GetObjectFromMode(LazyThreadSafetyMode mode)
|
|
{
|
|
if (mode == LazyThreadSafetyMode.ExecutionAndPublication)
|
|
return new object();
|
|
else if (mode == LazyThreadSafetyMode.PublicationOnly)
|
|
return LazyHelpers.PUBLICATION_ONLY_SENTINEL;
|
|
else if (mode != LazyThreadSafetyMode.None)
|
|
throw new ArgumentOutOfRangeException("mode", Environment.GetResourceString("Lazy_ctor_ModeInvalid"));
|
|
|
|
return null; // None mode
|
|
}
|
|
|
|
/// <summary>Forces initialization during serialization.</summary>
|
|
/// <param name="context">The StreamingContext for the serialization operation.</param>
|
|
[OnSerializing]
|
|
private void OnSerializing(StreamingContext context)
|
|
{
|
|
// Force initialization
|
|
T dummy = Value;
|
|
}
|
|
|
|
/// <summary>Creates and returns a string representation of this instance.</summary>
|
|
/// <returns>The result of calling <see cref="System.Object.ToString"/> on the <see
|
|
/// cref="Value"/>.</returns>
|
|
/// <exception cref="T:System.NullReferenceException">
|
|
/// The <see cref="Value"/> is null.
|
|
/// </exception>
|
|
public override string ToString()
|
|
{
|
|
return IsValueCreated ? Value.ToString() : Environment.GetResourceString("Lazy_ToString_ValueNotCreated");
|
|
}
|
|
|
|
/// <summary>Gets the value of the Lazy<T> for debugging display purposes.</summary>
|
|
internal T ValueForDebugDisplay
|
|
{
|
|
get
|
|
{
|
|
if (!IsValueCreated)
|
|
{
|
|
return default(T);
|
|
}
|
|
return ((Boxed)m_boxed).m_value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether this instance may be used concurrently from multiple threads.
|
|
/// </summary>
|
|
internal LazyThreadSafetyMode Mode
|
|
{
|
|
get
|
|
{
|
|
if (m_threadSafeObj == null) return LazyThreadSafetyMode.None;
|
|
if (m_threadSafeObj == (object)LazyHelpers.PUBLICATION_ONLY_SENTINEL) return LazyThreadSafetyMode.PublicationOnly;
|
|
return LazyThreadSafetyMode.ExecutionAndPublication;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether the value creation is faulted or not
|
|
/// </summary>
|
|
internal bool IsValueFaulted
|
|
{
|
|
get { return m_boxed is LazyInternalExceptionHolder; }
|
|
}
|
|
|
|
/// <summary>Gets a value indicating whether the <see cref="T:System.Lazy{T}"/> has been initialized.
|
|
/// </summary>
|
|
/// <value>true if the <see cref="T:System.Lazy{T}"/> instance has been initialized;
|
|
/// otherwise, false.</value>
|
|
/// <remarks>
|
|
/// The initialization of a <see cref="T:System.Lazy{T}"/> instance may result in either
|
|
/// a value being produced or an exception being thrown. If an exception goes unhandled during initialization,
|
|
/// <see cref="IsValueCreated"/> will return false.
|
|
/// </remarks>
|
|
public bool IsValueCreated
|
|
{
|
|
get
|
|
{
|
|
return m_boxed != null && m_boxed is Boxed;
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets the lazily initialized value of the current <see
|
|
/// cref="T:System.Threading.Lazy{T}"/>.</summary>
|
|
/// <value>The lazily initialized value of the current <see
|
|
/// cref="T:System.Threading.Lazy{T}"/>.</value>
|
|
/// <exception cref="T:System.MissingMemberException">
|
|
/// The <see cref="T:System.Threading.Lazy{T}"/> was initialized to use the default constructor
|
|
/// of the type being lazily initialized, and that type does not have a public, parameterless constructor.
|
|
/// </exception>
|
|
/// <exception cref="T:System.MemberAccessException">
|
|
/// The <see cref="T:System.Threading.Lazy{T}"/> was initialized to use the default constructor
|
|
/// of the type being lazily initialized, and permissions to access the constructor were missing.
|
|
/// </exception>
|
|
/// <exception cref="T:System.InvalidOperationException">
|
|
/// The <see cref="T:System.Threading.Lazy{T}"/> was constructed with the <see cref="T:System.Threading.LazyThreadSafetyMode.ExecutionAndPublication"/> or
|
|
/// <see cref="T:System.Threading.LazyThreadSafetyMode.None"/> and the initialization function attempted to access <see cref="Value"/> on this instance.
|
|
/// </exception>
|
|
/// <remarks>
|
|
/// If <see cref="IsValueCreated"/> is false, accessing <see cref="Value"/> will force initialization.
|
|
/// Please <see cref="System.Threading.LazyThreadSafetyMode"> for more information on how <see cref="T:System.Threading.Lazy{T}"/> will behave if an exception is thrown
|
|
/// from initialization delegate.
|
|
/// </remarks>
|
|
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
|
public T Value
|
|
{
|
|
get
|
|
{
|
|
Boxed boxed = null;
|
|
if (m_boxed != null )
|
|
{
|
|
// Do a quick check up front for the fast path.
|
|
boxed = m_boxed as Boxed;
|
|
if (boxed != null)
|
|
{
|
|
return boxed.m_value;
|
|
}
|
|
|
|
LazyInternalExceptionHolder exc = m_boxed as LazyInternalExceptionHolder;
|
|
Contract.Assert(m_boxed != null);
|
|
exc.m_edi.Throw();
|
|
}
|
|
|
|
// Fall through to the slow path.
|
|
#if !FEATURE_CORECLR
|
|
// We call NOCTD to abort attempts by the debugger to funceval this property (e.g. on mouseover)
|
|
// (the debugger proxy is the correct way to look at state/value of this object)
|
|
Debugger.NotifyOfCrossThreadDependency();
|
|
#endif
|
|
return LazyInitValue();
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// local helper method to initialize the value
|
|
/// </summary>
|
|
/// <returns>The inititialized T value</returns>
|
|
private T LazyInitValue()
|
|
{
|
|
Boxed boxed = null;
|
|
LazyThreadSafetyMode mode = Mode;
|
|
if (mode == LazyThreadSafetyMode.None)
|
|
{
|
|
boxed = CreateValue();
|
|
m_boxed = boxed;
|
|
}
|
|
else if (mode == LazyThreadSafetyMode.PublicationOnly)
|
|
{
|
|
boxed = CreateValue();
|
|
if (boxed == null ||
|
|
Interlocked.CompareExchange(ref m_boxed, boxed, null) != null)
|
|
{
|
|
// If CreateValue returns null, it means another thread successfully invoked the value factory
|
|
// and stored the result, so we should just take what was stored. If CreateValue returns non-null
|
|
// but we lose the ---- to store the single value, again we should just take what was stored.
|
|
boxed = (Boxed)m_boxed;
|
|
}
|
|
else
|
|
{
|
|
// We successfully created and stored the value. At this point, the value factory delegate is
|
|
// no longer needed, and we don't want to hold onto its resources.
|
|
m_valueFactory = ALREADY_INVOKED_SENTINEL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
object threadSafeObj = Volatile.Read(ref m_threadSafeObj);
|
|
bool lockTaken = false;
|
|
try
|
|
{
|
|
if (threadSafeObj != (object)ALREADY_INVOKED_SENTINEL)
|
|
Monitor.Enter(threadSafeObj, ref lockTaken);
|
|
else
|
|
Contract.Assert(m_boxed != null);
|
|
|
|
if (m_boxed == null)
|
|
{
|
|
boxed = CreateValue();
|
|
m_boxed = boxed;
|
|
Volatile.Write(ref m_threadSafeObj, ALREADY_INVOKED_SENTINEL);
|
|
}
|
|
else // got the lock but the value is not null anymore, check if it is created by another thread or faulted and throw if so
|
|
{
|
|
boxed = m_boxed as Boxed;
|
|
if (boxed == null) // it is not Boxed, so it is a LazyInternalExceptionHolder
|
|
{
|
|
LazyInternalExceptionHolder exHolder = m_boxed as LazyInternalExceptionHolder;
|
|
Contract.Assert(exHolder != null);
|
|
exHolder.m_edi.Throw();
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (lockTaken)
|
|
Monitor.Exit(threadSafeObj);
|
|
}
|
|
}
|
|
Contract.Assert(boxed != null);
|
|
return boxed.m_value;
|
|
}
|
|
|
|
/// <summary>Creates an instance of T using m_valueFactory in case its not null or use reflection to create a new T()</summary>
|
|
/// <returns>An instance of Boxed.</returns>
|
|
private Boxed CreateValue()
|
|
{
|
|
Boxed boxed = null;
|
|
LazyThreadSafetyMode mode = Mode;
|
|
if (m_valueFactory != null)
|
|
{
|
|
try
|
|
{
|
|
// check for recursion
|
|
if (mode != LazyThreadSafetyMode.PublicationOnly && m_valueFactory == ALREADY_INVOKED_SENTINEL)
|
|
throw new InvalidOperationException(Environment.GetResourceString("Lazy_Value_RecursiveCallsToValue"));
|
|
|
|
Func<T> factory = m_valueFactory;
|
|
if (mode != LazyThreadSafetyMode.PublicationOnly) // only detect recursion on None and ExecutionAndPublication modes
|
|
{
|
|
m_valueFactory = ALREADY_INVOKED_SENTINEL;
|
|
}
|
|
else if (factory == ALREADY_INVOKED_SENTINEL)
|
|
{
|
|
// Another thread raced with us and beat us to successfully invoke the factory.
|
|
return null;
|
|
}
|
|
boxed = new Boxed(factory());
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (mode != LazyThreadSafetyMode.PublicationOnly) // don't cache the exception for PublicationOnly mode
|
|
m_boxed = new LazyInternalExceptionHolder(ex);
|
|
throw;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
boxed = new Boxed((T)Activator.CreateInstance(typeof(T)));
|
|
|
|
}
|
|
catch (System.MissingMethodException)
|
|
{
|
|
Exception ex = new System.MissingMemberException(Environment.GetResourceString("Lazy_CreateValue_NoParameterlessCtorForT"));
|
|
if (mode != LazyThreadSafetyMode.PublicationOnly) // don't cache the exception for PublicationOnly mode
|
|
m_boxed = new LazyInternalExceptionHolder(ex);
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
return boxed;
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>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.</summary>
|
|
internal sealed class System_LazyDebugView<T>
|
|
{
|
|
//The Lazy object being viewed.
|
|
private readonly Lazy<T> m_lazy;
|
|
|
|
/// <summary>Constructs a new debugger view object for the provided Lazy object.</summary>
|
|
/// <param name="lazy">A Lazy object to browse in the debugger.</param>
|
|
public System_LazyDebugView(Lazy<T> lazy)
|
|
{
|
|
m_lazy = lazy;
|
|
}
|
|
|
|
/// <summary>Returns whether the Lazy object is initialized or not.</summary>
|
|
public bool IsValueCreated
|
|
{
|
|
get { return m_lazy.IsValueCreated; }
|
|
}
|
|
|
|
/// <summary>Returns the value of the Lazy object.</summary>
|
|
public T Value
|
|
{
|
|
get
|
|
{ return m_lazy.ValueForDebugDisplay; }
|
|
}
|
|
|
|
/// <summary>Returns the execution mode of the Lazy object</summary>
|
|
public LazyThreadSafetyMode Mode
|
|
{
|
|
get { return m_lazy.Mode; }
|
|
}
|
|
|
|
/// <summary>Returns the execution mode of the Lazy object</summary>
|
|
public bool IsValueFaulted
|
|
{
|
|
get { return m_lazy.IsValueFaulted; }
|
|
}
|
|
|
|
}
|
|
}
|