Lazy.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  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 class that provides a simple, lightweight implementation of lazy initialization,
  7. // obviating the need for a developer to implement a custom, thread-safe lazy initialization
  8. // solution.
  9. //
  10. // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  11. using System.Diagnostics;
  12. using System.Diagnostics.CodeAnalysis;
  13. using System.Runtime.ExceptionServices;
  14. using System.Threading;
  15. namespace System
  16. {
  17. internal enum LazyState
  18. {
  19. NoneViaConstructor = 0,
  20. NoneViaFactory = 1,
  21. NoneException = 2,
  22. PublicationOnlyViaConstructor = 3,
  23. PublicationOnlyViaFactory = 4,
  24. PublicationOnlyWait = 5,
  25. PublicationOnlyException = 6,
  26. ExecutionAndPublicationViaConstructor = 7,
  27. ExecutionAndPublicationViaFactory = 8,
  28. ExecutionAndPublicationException = 9,
  29. }
  30. /// <summary>
  31. /// LazyHelper serves multiples purposes
  32. /// - minimizing code size of Lazy&lt;T&gt; by implementing as much of the code that is not generic
  33. /// this reduces generic code bloat, making faster class initialization
  34. /// - contains singleton objects that are used to handle threading primitives for PublicationOnly mode
  35. /// - allows for instantiation for ExecutionAndPublication so as to create an object for locking on
  36. /// - holds exception information.
  37. /// </summary>
  38. internal class LazyHelper
  39. {
  40. internal static readonly LazyHelper NoneViaConstructor = new LazyHelper(LazyState.NoneViaConstructor);
  41. internal static readonly LazyHelper NoneViaFactory = new LazyHelper(LazyState.NoneViaFactory);
  42. internal static readonly LazyHelper PublicationOnlyViaConstructor = new LazyHelper(LazyState.PublicationOnlyViaConstructor);
  43. internal static readonly LazyHelper PublicationOnlyViaFactory = new LazyHelper(LazyState.PublicationOnlyViaFactory);
  44. internal static readonly LazyHelper PublicationOnlyWaitForOtherThreadToPublish = new LazyHelper(LazyState.PublicationOnlyWait);
  45. internal LazyState State { get; }
  46. private readonly ExceptionDispatchInfo? _exceptionDispatch;
  47. /// <summary>
  48. /// Constructor that defines the state
  49. /// </summary>
  50. internal LazyHelper(LazyState state)
  51. {
  52. State = state;
  53. }
  54. /// <summary>
  55. /// Constructor used for exceptions
  56. /// </summary>
  57. internal LazyHelper(LazyThreadSafetyMode mode, Exception exception)
  58. {
  59. switch (mode)
  60. {
  61. case LazyThreadSafetyMode.ExecutionAndPublication:
  62. State = LazyState.ExecutionAndPublicationException;
  63. break;
  64. case LazyThreadSafetyMode.None:
  65. State = LazyState.NoneException;
  66. break;
  67. case LazyThreadSafetyMode.PublicationOnly:
  68. State = LazyState.PublicationOnlyException;
  69. break;
  70. default:
  71. Debug.Fail("internal constructor, this should never occur");
  72. break;
  73. }
  74. _exceptionDispatch = ExceptionDispatchInfo.Capture(exception);
  75. }
  76. [DoesNotReturn]
  77. internal void ThrowException()
  78. {
  79. Debug.Assert(_exceptionDispatch != null, "execution path is invalid");
  80. _exceptionDispatch.Throw();
  81. }
  82. private LazyThreadSafetyMode GetMode()
  83. {
  84. switch (State)
  85. {
  86. case LazyState.NoneViaConstructor:
  87. case LazyState.NoneViaFactory:
  88. case LazyState.NoneException:
  89. return LazyThreadSafetyMode.None;
  90. case LazyState.PublicationOnlyViaConstructor:
  91. case LazyState.PublicationOnlyViaFactory:
  92. case LazyState.PublicationOnlyWait:
  93. case LazyState.PublicationOnlyException:
  94. return LazyThreadSafetyMode.PublicationOnly;
  95. case LazyState.ExecutionAndPublicationViaConstructor:
  96. case LazyState.ExecutionAndPublicationViaFactory:
  97. case LazyState.ExecutionAndPublicationException:
  98. return LazyThreadSafetyMode.ExecutionAndPublication;
  99. default:
  100. Debug.Fail("Invalid logic; State should always have a valid value");
  101. return default;
  102. }
  103. }
  104. internal static LazyThreadSafetyMode? GetMode(LazyHelper? state)
  105. {
  106. if (state == null)
  107. return null; // we don't know the mode anymore
  108. return state.GetMode();
  109. }
  110. internal static bool GetIsValueFaulted(LazyHelper? state) => state?._exceptionDispatch != null;
  111. internal static LazyHelper Create(LazyThreadSafetyMode mode, bool useDefaultConstructor)
  112. {
  113. switch (mode)
  114. {
  115. case LazyThreadSafetyMode.None:
  116. return useDefaultConstructor ? NoneViaConstructor : NoneViaFactory;
  117. case LazyThreadSafetyMode.PublicationOnly:
  118. return useDefaultConstructor ? PublicationOnlyViaConstructor : PublicationOnlyViaFactory;
  119. case LazyThreadSafetyMode.ExecutionAndPublication:
  120. // we need to create an object for ExecutionAndPublication because we use Monitor-based locking
  121. LazyState state = useDefaultConstructor ?
  122. LazyState.ExecutionAndPublicationViaConstructor :
  123. LazyState.ExecutionAndPublicationViaFactory;
  124. return new LazyHelper(state);
  125. default:
  126. throw new ArgumentOutOfRangeException(nameof(mode), SR.Lazy_ctor_ModeInvalid);
  127. }
  128. }
  129. internal static T CreateViaDefaultConstructor<T>()
  130. {
  131. try
  132. {
  133. return Activator.CreateInstance<T>();
  134. }
  135. catch (MissingMethodException)
  136. {
  137. throw new MissingMemberException(SR.Lazy_CreateValue_NoParameterlessCtorForT);
  138. }
  139. }
  140. internal static LazyThreadSafetyMode GetModeFromIsThreadSafe(bool isThreadSafe)
  141. {
  142. return isThreadSafe ? LazyThreadSafetyMode.ExecutionAndPublication : LazyThreadSafetyMode.None;
  143. }
  144. }
  145. /// <summary>
  146. /// Provides support for lazy initialization.
  147. /// </summary>
  148. /// <typeparam name="T">Specifies the type of element being lazily initialized.</typeparam>
  149. /// <remarks>
  150. /// <para>
  151. /// By default, all public and protected members of <see cref="Lazy{T}"/> are thread-safe and may be used
  152. /// concurrently from multiple threads. These thread-safety guarantees may be removed optionally and per instance
  153. /// using parameters to the type's constructors.
  154. /// </para>
  155. /// </remarks>
  156. [DebuggerTypeProxy(typeof(LazyDebugView<>))]
  157. [DebuggerDisplay("ThreadSafetyMode={Mode}, IsValueCreated={IsValueCreated}, IsValueFaulted={IsValueFaulted}, Value={ValueForDebugDisplay}")]
  158. public class Lazy<T>
  159. {
  160. private static T CreateViaDefaultConstructor() => LazyHelper.CreateViaDefaultConstructor<T>();
  161. // _state, a volatile reference, is set to null after _value has been set
  162. private volatile LazyHelper? _state;
  163. // we ensure that _factory when finished is set to null to allow garbage collector to clean up
  164. // any referenced items
  165. private Func<T>? _factory;
  166. // _value eventually stores the lazily created value. It is valid when _state = null.
  167. private T _value = default!;
  168. /// <summary>
  169. /// Initializes a new instance of the <see cref="System.Lazy{T}"/> class that
  170. /// uses <typeparamref name="T"/>'s default constructor for lazy initialization.
  171. /// </summary>
  172. /// <remarks>
  173. /// An instance created with this constructor may be used concurrently from multiple threads.
  174. /// </remarks>
  175. public Lazy()
  176. : this(null, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor: true)
  177. {
  178. }
  179. /// <summary>
  180. /// Initializes a new instance of the <see cref="System.Lazy{T}"/> class that
  181. /// uses a pre-initialized specified value.
  182. /// </summary>
  183. /// <remarks>
  184. /// An instance created with this constructor should be usable by multiple threads
  185. /// concurrently.
  186. /// </remarks>
  187. public Lazy(T value)
  188. {
  189. _value = value;
  190. }
  191. /// <summary>
  192. /// Initializes a new instance of the <see cref="System.Lazy{T}"/> class that uses a
  193. /// specified initialization function.
  194. /// </summary>
  195. /// <param name="valueFactory">
  196. /// The <see cref="System.Func{T}"/> invoked to produce the lazily-initialized value when it is
  197. /// needed.
  198. /// </param>
  199. /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is a null
  200. /// reference (Nothing in Visual Basic).</exception>
  201. /// <remarks>
  202. /// An instance created with this constructor may be used concurrently from multiple threads.
  203. /// </remarks>
  204. public Lazy(Func<T> valueFactory)
  205. : this(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor: false)
  206. {
  207. }
  208. /// <summary>
  209. /// Initializes a new instance of the <see cref="System.Lazy{T}"/>
  210. /// class that uses <typeparamref name="T"/>'s default constructor and a specified thread-safety mode.
  211. /// </summary>
  212. /// <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.
  213. /// </param>
  214. public Lazy(bool isThreadSafe) :
  215. this(null, LazyHelper.GetModeFromIsThreadSafe(isThreadSafe), useDefaultConstructor: true)
  216. {
  217. }
  218. /// <summary>
  219. /// Initializes a new instance of the <see cref="System.Lazy{T}"/>
  220. /// class that uses <typeparamref name="T"/>'s default constructor and a specified thread-safety mode.
  221. /// </summary>
  222. /// <param name="mode">The lazy thread-safety mode</param>
  223. /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="mode"/> mode contains an invalid valuee</exception>
  224. public Lazy(LazyThreadSafetyMode mode) :
  225. this(null, mode, useDefaultConstructor: true)
  226. {
  227. }
  228. /// <summary>
  229. /// Initializes a new instance of the <see cref="System.Lazy{T}"/> class
  230. /// that uses a specified initialization function and a specified thread-safety mode.
  231. /// </summary>
  232. /// <param name="valueFactory">
  233. /// The <see cref="System.Func{T}"/> invoked to produce the lazily-initialized value when it is needed.
  234. /// </param>
  235. /// <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.
  236. /// </param>
  237. /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is
  238. /// a null reference (Nothing in Visual Basic).</exception>
  239. public Lazy(Func<T> valueFactory, bool isThreadSafe) :
  240. this(valueFactory, LazyHelper.GetModeFromIsThreadSafe(isThreadSafe), useDefaultConstructor: false)
  241. {
  242. }
  243. /// <summary>
  244. /// Initializes a new instance of the <see cref="System.Lazy{T}"/> class
  245. /// that uses a specified initialization function and a specified thread-safety mode.
  246. /// </summary>
  247. /// <param name="valueFactory">
  248. /// The <see cref="System.Func{T}"/> invoked to produce the lazily-initialized value when it is needed.
  249. /// </param>
  250. /// <param name="mode">The lazy thread-safety mode.</param>
  251. /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is
  252. /// a null reference (Nothing in Visual Basic).</exception>
  253. /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="mode"/> mode contains an invalid value.</exception>
  254. public Lazy(Func<T> valueFactory, LazyThreadSafetyMode mode)
  255. : this(valueFactory, mode, useDefaultConstructor: false)
  256. {
  257. }
  258. private Lazy(Func<T>? valueFactory, LazyThreadSafetyMode mode, bool useDefaultConstructor)
  259. {
  260. if (valueFactory == null && !useDefaultConstructor)
  261. throw new ArgumentNullException(nameof(valueFactory));
  262. _factory = valueFactory;
  263. _state = LazyHelper.Create(mode, useDefaultConstructor);
  264. }
  265. private void ViaConstructor()
  266. {
  267. _value = CreateViaDefaultConstructor();
  268. _state = null; // volatile write, must occur after setting _value
  269. }
  270. private void ViaFactory(LazyThreadSafetyMode mode)
  271. {
  272. try
  273. {
  274. Func<T>? factory = _factory;
  275. if (factory == null)
  276. throw new InvalidOperationException(SR.Lazy_Value_RecursiveCallsToValue);
  277. _factory = null;
  278. _value = factory();
  279. _state = null; // volatile write, must occur after setting _value
  280. }
  281. catch (Exception exception)
  282. {
  283. _state = new LazyHelper(mode, exception);
  284. throw;
  285. }
  286. }
  287. private void ExecutionAndPublication(LazyHelper executionAndPublication, bool useDefaultConstructor)
  288. {
  289. lock (executionAndPublication)
  290. {
  291. // it's possible for multiple calls to have piled up behind the lock, so we need to check
  292. // to see if the ExecutionAndPublication object is still the current implementation.
  293. if (ReferenceEquals(_state, executionAndPublication))
  294. {
  295. if (useDefaultConstructor)
  296. {
  297. ViaConstructor();
  298. }
  299. else
  300. {
  301. ViaFactory(LazyThreadSafetyMode.ExecutionAndPublication);
  302. }
  303. }
  304. }
  305. }
  306. private void PublicationOnly(LazyHelper publicationOnly, T possibleValue)
  307. {
  308. LazyHelper? previous = Interlocked.CompareExchange(ref _state, LazyHelper.PublicationOnlyWaitForOtherThreadToPublish, publicationOnly);
  309. if (previous == publicationOnly)
  310. {
  311. _factory = null;
  312. _value = possibleValue;
  313. _state = null; // volatile write, must occur after setting _value
  314. }
  315. }
  316. private void PublicationOnlyViaConstructor(LazyHelper initializer)
  317. {
  318. PublicationOnly(initializer, CreateViaDefaultConstructor());
  319. }
  320. private void PublicationOnlyViaFactory(LazyHelper initializer)
  321. {
  322. Func<T>? factory = _factory;
  323. if (factory == null)
  324. {
  325. PublicationOnlyWaitForOtherThreadToPublish();
  326. }
  327. else
  328. {
  329. PublicationOnly(initializer, factory());
  330. }
  331. }
  332. private void PublicationOnlyWaitForOtherThreadToPublish()
  333. {
  334. SpinWait spinWait = default;
  335. while (!ReferenceEquals(_state, null))
  336. {
  337. // We get here when PublicationOnly temporarily sets _state to LazyHelper.PublicationOnlyWaitForOtherThreadToPublish.
  338. // This temporary state should be quickly followed by _state being set to null.
  339. spinWait.SpinOnce();
  340. }
  341. }
  342. private T CreateValue()
  343. {
  344. // we have to create a copy of state here, and use the copy exclusively from here on in
  345. // so as to ensure thread safety.
  346. LazyHelper? state = _state;
  347. if (state != null)
  348. {
  349. switch (state.State)
  350. {
  351. case LazyState.NoneViaConstructor:
  352. ViaConstructor();
  353. break;
  354. case LazyState.NoneViaFactory:
  355. ViaFactory(LazyThreadSafetyMode.None);
  356. break;
  357. case LazyState.PublicationOnlyViaConstructor:
  358. PublicationOnlyViaConstructor(state);
  359. break;
  360. case LazyState.PublicationOnlyViaFactory:
  361. PublicationOnlyViaFactory(state);
  362. break;
  363. case LazyState.PublicationOnlyWait:
  364. PublicationOnlyWaitForOtherThreadToPublish();
  365. break;
  366. case LazyState.ExecutionAndPublicationViaConstructor:
  367. ExecutionAndPublication(state, useDefaultConstructor: true);
  368. break;
  369. case LazyState.ExecutionAndPublicationViaFactory:
  370. ExecutionAndPublication(state, useDefaultConstructor: false);
  371. break;
  372. default:
  373. state.ThrowException();
  374. break;
  375. }
  376. }
  377. return Value;
  378. }
  379. /// <summary>Creates and returns a string representation of this instance.</summary>
  380. /// <returns>The result of calling <see cref="object.ToString"/> on the <see
  381. /// cref="Value"/>.</returns>
  382. /// <exception cref="System.NullReferenceException">
  383. /// The <see cref="Value"/> is null.
  384. /// </exception>
  385. public override string? ToString()
  386. {
  387. return IsValueCreated ?
  388. Value!.ToString() : // Throws NullReferenceException as if caller called ToString on the value itself
  389. SR.Lazy_ToString_ValueNotCreated;
  390. }
  391. /// <summary>Gets the value of the Lazy&lt;T&gt; for debugging display purposes.</summary>
  392. [MaybeNull]
  393. internal T ValueForDebugDisplay
  394. {
  395. get
  396. {
  397. if (!IsValueCreated)
  398. {
  399. return default!;
  400. }
  401. return _value;
  402. }
  403. }
  404. /// <summary>
  405. /// Gets a value indicating whether this instance may be used concurrently from multiple threads.
  406. /// </summary>
  407. internal LazyThreadSafetyMode? Mode => LazyHelper.GetMode(_state);
  408. /// <summary>
  409. /// Gets whether the value creation is faulted or not
  410. /// </summary>
  411. internal bool IsValueFaulted => LazyHelper.GetIsValueFaulted(_state);
  412. /// <summary>Gets a value indicating whether the <see cref="System.Lazy{T}"/> has been initialized.
  413. /// </summary>
  414. /// <value>true if the <see cref="System.Lazy{T}"/> instance has been initialized;
  415. /// otherwise, false.</value>
  416. /// <remarks>
  417. /// The initialization of a <see cref="System.Lazy{T}"/> instance may result in either
  418. /// a value being produced or an exception being thrown. If an exception goes unhandled during initialization,
  419. /// <see cref="IsValueCreated"/> will return false.
  420. /// </remarks>
  421. public bool IsValueCreated => _state == null;
  422. /// <summary>Gets the lazily initialized value of the current <see
  423. /// cref="System.Lazy{T}"/>.</summary>
  424. /// <value>The lazily initialized value of the current <see
  425. /// cref="System.Lazy{T}"/>.</value>
  426. /// <exception cref="System.MissingMemberException">
  427. /// The <see cref="System.Lazy{T}"/> was initialized to use the default constructor
  428. /// of the type being lazily initialized, and that type does not have a public, parameterless constructor.
  429. /// </exception>
  430. /// <exception cref="System.MemberAccessException">
  431. /// The <see cref="System.Lazy{T}"/> was initialized to use the default constructor
  432. /// of the type being lazily initialized, and permissions to access the constructor were missing.
  433. /// </exception>
  434. /// <exception cref="System.InvalidOperationException">
  435. /// The <see cref="System.Lazy{T}"/> was constructed with the <see cref="System.Threading.LazyThreadSafetyMode.ExecutionAndPublication"/> or
  436. /// <see cref="System.Threading.LazyThreadSafetyMode.None"/> and the initialization function attempted to access <see cref="Value"/> on this instance.
  437. /// </exception>
  438. /// <remarks>
  439. /// If <see cref="IsValueCreated"/> is false, accessing <see cref="Value"/> will force initialization.
  440. /// Please <see cref="System.Threading.LazyThreadSafetyMode"/> for more information on how <see cref="System.Lazy{T}"/> will behave if an exception is thrown
  441. /// from initialization delegate.
  442. /// </remarks>
  443. [DebuggerBrowsable(DebuggerBrowsableState.Never)]
  444. public T Value => _state == null ? _value : CreateValue();
  445. }
  446. /// <summary>A debugger view of the Lazy&lt;T&gt; to surface additional debugging properties and
  447. /// to ensure that the Lazy&lt;T&gt; does not become initialized if it was not already.</summary>
  448. internal sealed class LazyDebugView<T>
  449. {
  450. // The Lazy object being viewed.
  451. private readonly Lazy<T> _lazy;
  452. /// <summary>Constructs a new debugger view object for the provided Lazy object.</summary>
  453. /// <param name="lazy">A Lazy object to browse in the debugger.</param>
  454. public LazyDebugView(Lazy<T> lazy)
  455. {
  456. _lazy = lazy;
  457. }
  458. /// <summary>Returns whether the Lazy object is initialized or not.</summary>
  459. public bool IsValueCreated => _lazy.IsValueCreated;
  460. /// <summary>Returns the value of the Lazy object.</summary>
  461. public T Value => _lazy.ValueForDebugDisplay;
  462. /// <summary>Returns the execution mode of the Lazy object</summary>
  463. public LazyThreadSafetyMode? Mode => _lazy.Mode;
  464. /// <summary>Returns the execution mode of the Lazy object</summary>
  465. public bool IsValueFaulted => _lazy.IsValueFaulted;
  466. }
  467. }