2
0

LazyInitializer.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
  5. //
  6. // a set of lightweight static helpers for lazy initialization.
  7. //
  8. // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  9. using System.Diagnostics;
  10. namespace System.Threading
  11. {
  12. /// <summary>
  13. /// Provides lazy initialization routines.
  14. /// </summary>
  15. /// <remarks>
  16. /// These routines avoid needing to allocate a dedicated, lazy-initialization instance, instead using
  17. /// references to ensure targets have been initialized as they are accessed.
  18. /// </remarks>
  19. public static class LazyInitializer
  20. {
  21. /// <summary>
  22. /// Initializes a target reference type with the type's default constructor if the target has not
  23. /// already been initialized.
  24. /// </summary>
  25. /// <typeparam name="T">The reference type of the reference to be initialized.</typeparam>
  26. /// <param name="target">A reference of type <typeparamref name="T"/> to initialize if it has not
  27. /// already been initialized.</param>
  28. /// <returns>The initialized reference of type <typeparamref name="T"/>.</returns>
  29. /// <exception cref="T:System.MissingMemberException">Type <typeparamref name="T"/> does not have a default
  30. /// constructor.</exception>
  31. /// <exception cref="T:System.MemberAccessException">
  32. /// Permissions to access the constructor of type <typeparamref name="T"/> were missing.
  33. /// </exception>
  34. /// <remarks>
  35. /// <para>
  36. /// This method may only be used on reference types. To ensure initialization of value
  37. /// types, see other overloads of EnsureInitialized.
  38. /// </para>
  39. /// <para>
  40. /// This method may be used concurrently by multiple threads to initialize <paramref name="target"/>.
  41. /// In the event that multiple threads access this method concurrently, multiple instances of <typeparamref name="T"/>
  42. /// may be created, but only one will be stored into <paramref name="target"/>. In such an occurrence, this method will not dispose of the
  43. /// objects that were not stored. If such objects must be disposed, it is up to the caller to determine
  44. /// if an object was not used and to then dispose of the object appropriately.
  45. /// </para>
  46. /// </remarks>
  47. public static T EnsureInitialized<T>(ref T target) where T : class =>
  48. Volatile.Read(ref target) ?? EnsureInitializedCore(ref target);
  49. /// <summary>
  50. /// Initializes a target reference type with the type's default constructor (slow path)
  51. /// </summary>
  52. /// <typeparam name="T">The reference type of the reference to be initialized.</typeparam>
  53. /// <param name="target">The variable that need to be initialized</param>
  54. /// <returns>The initialized variable</returns>
  55. private static T EnsureInitializedCore<T>(ref T target) where T : class
  56. {
  57. try
  58. {
  59. Interlocked.CompareExchange(ref target, Activator.CreateInstance<T>(), null);
  60. }
  61. catch (MissingMethodException)
  62. {
  63. throw new MissingMemberException(SR.Lazy_CreateValue_NoParameterlessCtorForT);
  64. }
  65. Debug.Assert(target != null);
  66. return target;
  67. }
  68. /// <summary>
  69. /// Initializes a target reference type using the specified function if it has not already been
  70. /// initialized.
  71. /// </summary>
  72. /// <typeparam name="T">The reference type of the reference to be initialized.</typeparam>
  73. /// <param name="target">The reference of type <typeparamref name="T"/> to initialize if it has not
  74. /// already been initialized.</param>
  75. /// <param name="valueFactory">The <see cref="T:System.Func{T}"/> invoked to initialize the
  76. /// reference.</param>
  77. /// <returns>The initialized reference of type <typeparamref name="T"/>.</returns>
  78. /// <exception cref="T:System.MissingMemberException">Type <typeparamref name="T"/> does not have a
  79. /// default constructor.</exception>
  80. /// <exception cref="T:System.InvalidOperationException"><paramref name="valueFactory"/> returned
  81. /// null.</exception>
  82. /// <remarks>
  83. /// <para>
  84. /// This method may only be used on reference types, and <paramref name="valueFactory"/> may
  85. /// not return a null reference (Nothing in Visual Basic). To ensure initialization of value types or
  86. /// to allow null reference types, see other overloads of EnsureInitialized.
  87. /// </para>
  88. /// <para>
  89. /// This method may be used concurrently by multiple threads to initialize <paramref name="target"/>.
  90. /// In the event that multiple threads access this method concurrently, multiple instances of <typeparamref name="T"/>
  91. /// may be created, but only one will be stored into <paramref name="target"/>. In such an occurrence, this method will not dispose of the
  92. /// objects that were not stored. If such objects must be disposed, it is up to the caller to determine
  93. /// if an object was not used and to then dispose of the object appropriately.
  94. /// </para>
  95. /// </remarks>
  96. public static T EnsureInitialized<T>(ref T target, Func<T> valueFactory) where T : class =>
  97. Volatile.Read(ref target) ?? EnsureInitializedCore(ref target, valueFactory);
  98. /// <summary>
  99. /// Initialize the target using the given delegate (slow path).
  100. /// </summary>
  101. /// <typeparam name="T">The reference type of the reference to be initialized.</typeparam>
  102. /// <param name="target">The variable that need to be initialized</param>
  103. /// <param name="valueFactory">The delegate that will be executed to initialize the target</param>
  104. /// <returns>The initialized variable</returns>
  105. private static T EnsureInitializedCore<T>(ref T target, Func<T> valueFactory) where T : class
  106. {
  107. T value = valueFactory();
  108. if (value == null)
  109. {
  110. throw new InvalidOperationException(SR.Lazy_StaticInit_InvalidOperation);
  111. }
  112. Interlocked.CompareExchange(ref target, value, null);
  113. Debug.Assert(target != null);
  114. return target;
  115. }
  116. /// <summary>
  117. /// Initializes a target reference or value type with its default constructor if it has not already
  118. /// been initialized.
  119. /// </summary>
  120. /// <typeparam name="T">The type of the reference to be initialized.</typeparam>
  121. /// <param name="target">A reference or value of type <typeparamref name="T"/> to initialize if it
  122. /// has not already been initialized.</param>
  123. /// <param name="initialized">A reference to a boolean that determines whether the target has already
  124. /// been initialized.</param>
  125. /// <param name="syncLock">A reference to an object used as the mutually exclusive lock for initializing
  126. /// <paramref name="target"/>. If <paramref name="syncLock"/> is null, a new object will be instantiated.</param>
  127. /// <returns>The initialized value of type <typeparamref name="T"/>.</returns>
  128. public static T EnsureInitialized<T>(ref T target, ref bool initialized, ref object syncLock)
  129. {
  130. // Fast path.
  131. if (Volatile.Read(ref initialized))
  132. {
  133. return target;
  134. }
  135. return EnsureInitializedCore(ref target, ref initialized, ref syncLock);
  136. }
  137. /// <summary>
  138. /// Ensure the target is initialized and return the value (slow path). This overload permits nulls
  139. /// and also works for value type targets. Uses the type's default constructor to create the value.
  140. /// </summary>
  141. /// <typeparam name="T">The type of target.</typeparam>
  142. /// <param name="target">A reference to the target to be initialized.</param>
  143. /// <param name="initialized">A reference to a location tracking whether the target has been initialized.</param>
  144. /// <param name="syncLock">A reference to a location containing a mutual exclusive lock. If <paramref name="syncLock"/> is null,
  145. /// a new object will be instantiated.
  146. /// </param>
  147. /// <returns>The initialized object.</returns>
  148. private static T EnsureInitializedCore<T>(ref T target, ref bool initialized, ref object syncLock)
  149. {
  150. // Lazily initialize the lock if necessary and then double check if initialization is still required.
  151. lock (EnsureLockInitialized(ref syncLock))
  152. {
  153. if (!Volatile.Read(ref initialized))
  154. {
  155. try
  156. {
  157. target = Activator.CreateInstance<T>();
  158. }
  159. catch (MissingMethodException)
  160. {
  161. throw new MissingMemberException(SR.Lazy_CreateValue_NoParameterlessCtorForT);
  162. }
  163. Volatile.Write(ref initialized, true);
  164. }
  165. }
  166. return target;
  167. }
  168. /// <summary>
  169. /// Initializes a target reference or value type with a specified function if it has not already been
  170. /// initialized.
  171. /// </summary>
  172. /// <typeparam name="T">The type of the reference to be initialized.</typeparam>
  173. /// <param name="target">A reference or value of type <typeparamref name="T"/> to initialize if it
  174. /// has not already been initialized.</param>
  175. /// <param name="initialized">A reference to a boolean that determines whether the target has already
  176. /// been initialized.</param>
  177. /// <param name="syncLock">A reference to an object used as the mutually exclusive lock for initializing
  178. /// <paramref name="target"/>. If <paramref name="syncLock"/> is null, a new object will be instantiated.</param>
  179. /// <param name="valueFactory">The <see cref="T:System.Func{T}"/> invoked to initialize the
  180. /// reference or value.</param>
  181. /// <returns>The initialized value of type <typeparamref name="T"/>.</returns>
  182. public static T EnsureInitialized<T>(ref T target, ref bool initialized, ref object syncLock, Func<T> valueFactory)
  183. {
  184. // Fast path.
  185. if (Volatile.Read(ref initialized))
  186. {
  187. return target;
  188. }
  189. return EnsureInitializedCore(ref target, ref initialized, ref syncLock, valueFactory);
  190. }
  191. /// <summary>
  192. /// Ensure the target is initialized and return the value (slow path). This overload permits nulls
  193. /// and also works for value type targets. Uses the supplied function to create the value.
  194. /// </summary>
  195. /// <typeparam name="T">The type of target.</typeparam>
  196. /// <param name="target">A reference to the target to be initialized.</param>
  197. /// <param name="initialized">A reference to a location tracking whether the target has been initialized.</param>
  198. /// <param name="syncLock">A reference to a location containing a mutual exclusive lock. If <paramref name="syncLock"/> is null,
  199. /// a new object will be instantiated.</param>
  200. /// <param name="valueFactory">
  201. /// The <see cref="T:System.Func{T}"/> to invoke in order to produce the lazily-initialized value.
  202. /// </param>
  203. /// <returns>The initialized object.</returns>
  204. private static T EnsureInitializedCore<T>(ref T target, ref bool initialized, ref object syncLock, Func<T> valueFactory)
  205. {
  206. // Lazily initialize the lock if necessary and then double check if initialization is still required.
  207. lock (EnsureLockInitialized(ref syncLock))
  208. {
  209. if (!Volatile.Read(ref initialized))
  210. {
  211. target = valueFactory();
  212. Volatile.Write(ref initialized, true);
  213. }
  214. }
  215. return target;
  216. }
  217. /// <summary>
  218. /// Initializes a target reference type with a specified function if it has not already been initialized.
  219. /// </summary>
  220. /// <typeparam name="T">The type of the reference to be initialized. Has to be reference type.</typeparam>
  221. /// <param name="target">A reference of type <typeparamref name="T"/> to initialize if it has not already been initialized.</param>
  222. /// <param name="syncLock">A reference to an object used as the mutually exclusive lock for initializing
  223. /// <paramref name="target"/>. If <paramref name="syncLock"/> is null, a new object will be instantiated.</param>
  224. /// <param name="valueFactory">The <see cref="T:System.Func{T}"/> invoked to initialize the reference.</param>
  225. /// <returns>The initialized value of type <typeparamref name="T"/>.</returns>
  226. public static T EnsureInitialized<T>(ref T target, ref object syncLock, Func<T> valueFactory) where T : class =>
  227. Volatile.Read(ref target) ?? EnsureInitializedCore(ref target, ref syncLock, valueFactory);
  228. /// <summary>
  229. /// Ensure the target is initialized and return the value (slow path). This overload works only for reference type targets.
  230. /// Uses the supplied function to create the value.
  231. /// </summary>
  232. /// <typeparam name="T">The type of target. Has to be reference type.</typeparam>
  233. /// <param name="target">A reference to the target to be initialized.</param>
  234. /// <param name="syncLock">A reference to a location containing a mutual exclusive lock. If <paramref name="syncLock"/> is null,
  235. /// a new object will be instantiated.</param>
  236. /// <param name="valueFactory">
  237. /// The <see cref="T:System.Func{T}"/> to invoke in order to produce the lazily-initialized value.
  238. /// </param>
  239. /// <returns>The initialized object.</returns>
  240. private static T EnsureInitializedCore<T>(ref T target, ref object syncLock, Func<T> valueFactory) where T : class
  241. {
  242. // Lazily initialize the lock if necessary and then double check if initialization is still required.
  243. lock (EnsureLockInitialized(ref syncLock))
  244. {
  245. if (Volatile.Read(ref target) == null)
  246. {
  247. Volatile.Write(ref target, valueFactory());
  248. if (target == null)
  249. {
  250. throw new InvalidOperationException(SR.Lazy_StaticInit_InvalidOperation);
  251. }
  252. }
  253. }
  254. return target;
  255. }
  256. /// <summary>
  257. /// Ensure the lock object is initialized.
  258. /// </summary>
  259. /// <param name="syncLock">A reference to a location containing a mutual exclusive lock. If <paramref name="syncLock"/> is null,
  260. /// a new object will be instantiated.</param>
  261. /// <returns>Initialized lock object.</returns>
  262. private static object EnsureLockInitialized(ref object syncLock) =>
  263. syncLock ??
  264. Interlocked.CompareExchange(ref syncLock, new object(), null) ??
  265. syncLock;
  266. }
  267. }