diff --git a/src/System.Private.CoreLib/src/System/Threading/LazyInitializer.cs b/src/System.Private.CoreLib/src/System/Threading/LazyInitializer.cs index 5b1d6251187..5853353661e 100644 --- a/src/System.Private.CoreLib/src/System/Threading/LazyInitializer.cs +++ b/src/System.Private.CoreLib/src/System/Threading/LazyInitializer.cs @@ -47,16 +47,8 @@ public static class LazyInitializer /// if an object was not used and to then dispose of the object appropriately. /// /// - public static T EnsureInitialized(ref T target) where T : class - { - // Fast path. - if (Volatile.Read(ref target) != null) - { - return target; - } - - return EnsureInitializedCore(ref target, LazyHelpers.ActivatorFactorySelectorFunc); - } + public static T EnsureInitialized(ref T target) where T : class => + Volatile.Read(ref target) ?? EnsureInitializedCore(ref target, LazyHelpers.ActivatorFactorySelectorFunc); /// /// Initializes a target reference type using the specified function if it has not already been @@ -86,17 +78,8 @@ public static T EnsureInitialized(ref T target) where T : class /// if an object was not used and to then dispose of the object appropriately. /// /// - public static T EnsureInitialized(ref T target, Func valueFactory) where T : class - { - // Fast path. - // According to the abstract ECMA CLI memory model, this read should be volatile, due to possible reorderings with - // subsequent reads from fields of "target." However, as of today (7/12/12), Volatile.Read is quite slow, and in practice - // this read does *not* need to be volatile, because our memory model preserves ordering of data-dependent reads. - if (target != null) - return target; - - return EnsureInitializedCore(ref target, valueFactory); - } + public static T EnsureInitialized(ref T target, Func valueFactory) where T : class => + Volatile.Read(ref target) ?? EnsureInitializedCore(ref target, valueFactory); /// /// Initialize the target using the given delegate (slow path). @@ -168,6 +151,29 @@ public static T EnsureInitialized(ref T target, ref bool initialized, ref obj return EnsureInitializedCore(ref target, ref initialized, ref syncLock, valueFactory); } + /// + /// Ensure the lock object is intialized. + /// + /// A reference to a location containing a mutual exclusive lock. If is null, + /// a new object will be instantiated. + /// Initialized lock object. + private static object EnsureLockInitialized(ref object syncLock) => + syncLock ?? + Interlocked.CompareExchange(ref syncLock, new object(), null) ?? + syncLock; + + /// + /// Initializes a target reference type with a specified function if it has not already been initialized. + /// + /// The type of the reference to be initialized. Has to be reference type. + /// A reference of type to initialize if it has not already been initialized. + /// A reference to an object used as the mutually exclusive lock for initializing + /// . If is null, a new object will be instantiated. + /// The invoked to initialize the reference. + /// The initialized value of type . + public static T EnsureInitialized(ref T target, ref object syncLock, Func valueFactory) where T : class => + Volatile.Read(ref target) ?? EnsureInitializedCore(ref target, ref syncLock, valueFactory); + /// /// 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. @@ -183,25 +189,43 @@ public static T EnsureInitialized(ref T target, ref bool initialized, ref obj /// The initialized object. private static T EnsureInitializedCore(ref T target, ref bool initialized, ref object syncLock, Func valueFactory) { - // Lazily initialize the lock if necessary. - object slock = syncLock; - if (slock == null) + // Lazily initialize the lock if necessary and, then double check if initialization is still required. + lock (EnsureLockInitialized(ref syncLock)) { - object newLock = new object(); - slock = Interlocked.CompareExchange(ref syncLock, newLock, null); - if (slock == null) + if (!Volatile.Read(ref initialized)) { - slock = newLock; + target = valueFactory(); + Volatile.Write(ref initialized, true); } } - // Now double check that initialization is still required. - lock (slock) + return target; + } + + /// + /// 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. + /// + /// The type of target. Has to be reference type. + /// A reference to the target to be initialized. + /// A reference to a location containing a mutual exclusive lock. If is null, + /// a new object will be instantiated. + /// + /// The to invoke in order to produce the lazily-initialized value. + /// + /// The initialized object. + private static T EnsureInitializedCore(ref T target, ref object syncLock, Func 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 initialized)) + if (Volatile.Read(ref target) == null) { - target = valueFactory(); - Volatile.Write(ref initialized, true); + Volatile.Write(ref target, valueFactory()); + if (target == null) + { + throw new InvalidOperationException(SR.Lazy_StaticInit_InvalidOperation); + } } }