// 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(LazyThreadSafetyMode);
}
}
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(T);
}
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; }
}
}
}