diff options
Diffstat (limited to 'src/mscorlib/shared')
-rw-r--r-- | src/mscorlib/shared/System.Private.CoreLib.Shared.projitems | 1 | ||||
-rw-r--r-- | src/mscorlib/shared/System/Lazy.cs | 562 |
2 files changed, 563 insertions, 0 deletions
diff --git a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems index 2ca7c35bd4..09b940c4d0 100644 --- a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems @@ -116,6 +116,7 @@ <Compile Include="$(MSBuildThisFileDirectory)System\IObservable.cs"/> <Compile Include="$(MSBuildThisFileDirectory)System\IObserver.cs"/> <Compile Include="$(MSBuildThisFileDirectory)System\IProgress.cs"/> + <Compile Include="$(MSBuildThisFileDirectory)System\Lazy.cs"/> <Compile Include="$(MSBuildThisFileDirectory)System\MarshalByRefObject.cs"/> <Compile Include="$(MSBuildThisFileDirectory)System\MemberAccessException.cs"/> <Compile Include="$(MSBuildThisFileDirectory)System\MethodAccessException.cs"/> diff --git a/src/mscorlib/shared/System/Lazy.cs b/src/mscorlib/shared/System/Lazy.cs new file mode 100644 index 0000000000..d59f67849f --- /dev/null +++ b/src/mscorlib/shared/System/Lazy.cs @@ -0,0 +1,562 @@ +// 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.Runtime.Serialization; +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, + } + + /// <summary> + /// 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. + /// </summary> + 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; + + /// <summary> + /// Constructor that defines the state + /// </summary> + internal LazyHelper(LazyState state) + { + State = state; + } + + /// <summary> + /// Constructor used for exceptions + /// </summary> + 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.Assert(false, "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.Assert(false, "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; + } + } + + /// <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)] + [DebuggerTypeProxy(typeof(System_LazyDebugView<>))] + [DebuggerDisplay("ThreadSafetyMode={Mode}, IsValueCreated={IsValueCreated}, IsValueFaulted={IsValueFaulted}, Value={ValueForDebugDisplay}")] + public class Lazy<T> + { + private static T CreateViaDefaultConstructor() + { + return (T)LazyHelper.CreateViaDefaultConstructor(typeof(T)); + } + + // _state, a volatile reference, is set to null after m_value has been set + [NonSerialized] + private volatile LazyHelper _state; + + // we ensure that _factory when finished is set to null to allow garbage collector to clean up + // any referenced items + [NonSerialized] + private Func<T> _factory; + + // m_value eventually stores the lazily created value. It is valid when _state = null. + private T _value; + + /// <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(null, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor:true) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class that + /// uses a pre-initialized specified value. + /// </summary> + /// <remarks> + /// An instance created with this constructor should be usable by multiple threads + // concurrently. + /// </remarks> + public Lazy(T value) + { + _value = value; + } + + /// <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, useDefaultConstructor:false) + { + } + + /// <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(null, LazyHelper.GetModeFromIsThreadSafe(isThreadSafe), useDefaultConstructor:true) + { + } + + /// <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) : + this(null, mode, useDefaultConstructor:true) + { + } + + /// <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, LazyHelper.GetModeFromIsThreadSafe(isThreadSafe), useDefaultConstructor:false) + { + } + + /// <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) + : this(valueFactory, mode, useDefaultConstructor:false) + { + } + + private Lazy(Func<T> 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<T> 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<T> 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; + } + + /// <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() : SR.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 _value; + } + } + + /// <summary> + /// Gets a value indicating whether this instance may be used concurrently from multiple threads. + /// </summary> + internal LazyThreadSafetyMode? Mode => LazyHelper.GetMode(_state); + + /// <summary> + /// Gets whether the value creation is faulted or not + /// </summary> + internal bool IsValueFaulted => LazyHelper.GetIsValueFaulted(_state); + + /// <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 => _state == null; + + /// <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 => _state == null ? _value : CreateValue(); + } + + /// <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> _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) + { + _lazy = lazy; + } + + /// <summary>Returns whether the Lazy object is initialized or not.</summary> + public bool IsValueCreated + { + get { return _lazy.IsValueCreated; } + } + + /// <summary>Returns the value of the Lazy object.</summary> + public T Value + { + get + { return _lazy.ValueForDebugDisplay; } + } + + /// <summary>Returns the execution mode of the Lazy object</summary> + public LazyThreadSafetyMode? Mode + { + get { return _lazy.Mode; } + } + + /// <summary>Returns the execution mode of the Lazy object</summary> + public bool IsValueFaulted + { + get { return _lazy.IsValueFaulted; } + } + } +} |