Lazy.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  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. var state = useDefaultConstructor ? LazyState.ExecutionAndPublicationViaConstructor : LazyState.ExecutionAndPublicationViaFactory;
  122. return new LazyHelper(state);
  123. default:
  124. throw new ArgumentOutOfRangeException(nameof(mode), SR.Lazy_ctor_ModeInvalid);
  125. }
  126. }
  127. internal static T CreateViaDefaultConstructor<T>()
  128. {
  129. try
  130. {
  131. return Activator.CreateInstance<T>();
  132. }
  133. catch (MissingMethodException)
  134. {
  135. throw new MissingMemberException(SR.Lazy_CreateValue_NoParameterlessCtorForT);
  136. }
  137. }
  138. internal static LazyThreadSafetyMode GetModeFromIsThreadSafe(bool isThreadSafe)
  139. {
  140. return isThreadSafe ? LazyThreadSafetyMode.ExecutionAndPublication : LazyThreadSafetyMode.None;
  141. }
  142. }
  143. /// <summary>
  144. /// Provides support for lazy initialization.
  145. /// </summary>
  146. /// <typeparam name="T">Specifies the type of element being lazily initialized.</typeparam>
  147. /// <remarks>
  148. /// <para>
  149. /// By default, all public and protected members of <see cref="Lazy{T}"/> are thread-safe and may be used
  150. /// concurrently from multiple threads. These thread-safety guarantees may be removed optionally and per instance
  151. /// using parameters to the type's constructors.
  152. /// </para>
  153. /// </remarks>
  154. [DebuggerTypeProxy(typeof(LazyDebugView<>))]
  155. [DebuggerDisplay("ThreadSafetyMode={Mode}, IsValueCreated={IsValueCreated}, IsValueFaulted={IsValueFaulted}, Value={ValueForDebugDisplay}")]
  156. public class Lazy<T>
  157. {
  158. private static T CreateViaDefaultConstructor() => LazyHelper.CreateViaDefaultConstructor<T>();
  159. // _state, a volatile reference, is set to null after _value has been set
  160. private volatile LazyHelper? _state;
  161. // we ensure that _factory when finished is set to null to allow garbage collector to clean up
  162. // any referenced items
  163. private Func<T>? _factory;
  164. // _value eventually stores the lazily created value. It is valid when _state = null.
  165. private T _value = default!;
  166. /// <summary>
  167. /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class that
  168. /// uses <typeparamref name="T"/>'s default constructor for lazy initialization.
  169. /// </summary>
  170. /// <remarks>
  171. /// An instance created with this constructor may be used concurrently from multiple threads.
  172. /// </remarks>
  173. public Lazy()
  174. : this(null, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor: true)
  175. {
  176. }
  177. /// <summary>
  178. /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class that
  179. /// uses a pre-initialized specified value.
  180. /// </summary>
  181. /// <remarks>
  182. /// An instance created with this constructor should be usable by multiple threads
  183. /// concurrently.
  184. /// </remarks>
  185. public Lazy(T value)
  186. {
  187. _value = value;
  188. }
  189. /// <summary>
  190. /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class that uses a
  191. /// specified initialization function.
  192. /// </summary>
  193. /// <param name="valueFactory">
  194. /// The <see cref="T:System.Func{T}"/> invoked to produce the lazily-initialized value when it is
  195. /// needed.
  196. /// </param>
  197. /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is a null
  198. /// reference (Nothing in Visual Basic).</exception>
  199. /// <remarks>
  200. /// An instance created with this constructor may be used concurrently from multiple threads.
  201. /// </remarks>
  202. public Lazy(Func<T> valueFactory)
  203. : this(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor: false)
  204. {
  205. }
  206. /// <summary>
  207. /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/>
  208. /// class that uses <typeparamref name="T"/>'s default constructor and a specified thread-safety mode.
  209. /// </summary>
  210. /// <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.
  211. /// </param>
  212. public Lazy(bool isThreadSafe) :
  213. this(null, LazyHelper.GetModeFromIsThreadSafe(isThreadSafe), useDefaultConstructor: true)
  214. {
  215. }
  216. /// <summary>
  217. /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/>
  218. /// class that uses <typeparamref name="T"/>'s default constructor and a specified thread-safety mode.
  219. /// </summary>
  220. /// <param name="mode">The lazy thread-safety mode</param>
  221. /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="mode"/> mode contains an invalid valuee</exception>
  222. public Lazy(LazyThreadSafetyMode mode) :
  223. this(null, mode, useDefaultConstructor:true)
  224. {
  225. }
  226. /// <summary>
  227. /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class
  228. /// that uses a specified initialization function and a specified thread-safety mode.
  229. /// </summary>
  230. /// <param name="valueFactory">
  231. /// The <see cref="T:System.Func{T}"/> invoked to produce the lazily-initialized value when it is needed.
  232. /// </param>
  233. /// <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.
  234. /// </param>
  235. /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is
  236. /// a null reference (Nothing in Visual Basic).</exception>
  237. public Lazy(Func<T> valueFactory, bool isThreadSafe) :
  238. this(valueFactory, LazyHelper.GetModeFromIsThreadSafe(isThreadSafe), useDefaultConstructor: false)
  239. {
  240. }
  241. /// <summary>
  242. /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class
  243. /// that uses a specified initialization function and a specified thread-safety mode.
  244. /// </summary>
  245. /// <param name="valueFactory">
  246. /// The <see cref="T:System.Func{T}"/> invoked to produce the lazily-initialized value when it is needed.
  247. /// </param>
  248. /// <param name="mode">The lazy thread-safety mode.</param>
  249. /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is
  250. /// a null reference (Nothing in Visual Basic).</exception>
  251. /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="mode"/> mode contains an invalid value.</exception>
  252. public Lazy(Func<T> valueFactory, LazyThreadSafetyMode mode)
  253. : this(valueFactory, mode, useDefaultConstructor:false)
  254. {
  255. }
  256. private Lazy(Func<T>? valueFactory, LazyThreadSafetyMode mode, bool useDefaultConstructor)
  257. {
  258. if (valueFactory == null && !useDefaultConstructor)
  259. throw new ArgumentNullException(nameof(valueFactory));
  260. _factory = valueFactory;
  261. _state = LazyHelper.Create(mode, useDefaultConstructor);
  262. }
  263. private void ViaConstructor()
  264. {
  265. _value = CreateViaDefaultConstructor();
  266. _state = null; // volatile write, must occur after setting _value
  267. }
  268. private void ViaFactory(LazyThreadSafetyMode mode)
  269. {
  270. try
  271. {
  272. Func<T>? factory = _factory;
  273. if (factory == null)
  274. throw new InvalidOperationException(SR.Lazy_Value_RecursiveCallsToValue);
  275. _factory = null;
  276. _value = factory();
  277. _state = null; // volatile write, must occur after setting _value
  278. }
  279. catch (Exception exception)
  280. {
  281. _state = new LazyHelper(mode, exception);
  282. throw;
  283. }
  284. }
  285. private void ExecutionAndPublication(LazyHelper executionAndPublication, bool useDefaultConstructor)
  286. {
  287. lock (executionAndPublication)
  288. {
  289. // it's possible for multiple calls to have piled up behind the lock, so we need to check
  290. // to see if the ExecutionAndPublication object is still the current implementation.
  291. if (ReferenceEquals(_state, executionAndPublication))
  292. {
  293. if (useDefaultConstructor)
  294. {
  295. ViaConstructor();
  296. }
  297. else
  298. {
  299. ViaFactory(LazyThreadSafetyMode.ExecutionAndPublication);
  300. }
  301. }
  302. }
  303. }
  304. private void PublicationOnly(LazyHelper publicationOnly, T possibleValue)
  305. {
  306. LazyHelper? previous = Interlocked.CompareExchange(ref _state, LazyHelper.PublicationOnlyWaitForOtherThreadToPublish, publicationOnly);
  307. if (previous == publicationOnly)
  308. {
  309. _factory = null;
  310. _value = possibleValue;
  311. _state = null; // volatile write, must occur after setting _value
  312. }
  313. }
  314. private void PublicationOnlyViaConstructor(LazyHelper initializer)
  315. {
  316. PublicationOnly(initializer, CreateViaDefaultConstructor());
  317. }
  318. private void PublicationOnlyViaFactory(LazyHelper initializer)
  319. {
  320. Func<T>? factory = _factory;
  321. if (factory == null)
  322. {
  323. PublicationOnlyWaitForOtherThreadToPublish();
  324. }
  325. else
  326. {
  327. PublicationOnly(initializer, factory());
  328. }
  329. }
  330. private void PublicationOnlyWaitForOtherThreadToPublish()
  331. {
  332. var spinWait = new SpinWait();
  333. while (!ReferenceEquals(_state, null))
  334. {
  335. // We get here when PublicationOnly temporarily sets _state to LazyHelper.PublicationOnlyWaitForOtherThreadToPublish.
  336. // This temporary state should be quickly followed by _state being set to null.
  337. spinWait.SpinOnce();
  338. }
  339. }
  340. private T CreateValue()
  341. {
  342. // we have to create a copy of state here, and use the copy exclusively from here on in
  343. // so as to ensure thread safety.
  344. LazyHelper? state = _state;
  345. if (state != null)
  346. {
  347. switch (state.State)
  348. {
  349. case LazyState.NoneViaConstructor:
  350. ViaConstructor();
  351. break;
  352. case LazyState.NoneViaFactory:
  353. ViaFactory(LazyThreadSafetyMode.None);
  354. break;
  355. case LazyState.PublicationOnlyViaConstructor:
  356. PublicationOnlyViaConstructor(state);
  357. break;
  358. case LazyState.PublicationOnlyViaFactory:
  359. PublicationOnlyViaFactory(state);
  360. break;
  361. case LazyState.PublicationOnlyWait:
  362. PublicationOnlyWaitForOtherThreadToPublish();
  363. break;
  364. case LazyState.ExecutionAndPublicationViaConstructor:
  365. ExecutionAndPublication(state, useDefaultConstructor:true);
  366. break;
  367. case LazyState.ExecutionAndPublicationViaFactory:
  368. ExecutionAndPublication(state, useDefaultConstructor:false);
  369. break;
  370. default:
  371. state.ThrowException();
  372. break;
  373. }
  374. }
  375. return Value;
  376. }
  377. /// <summary>Creates and returns a string representation of this instance.</summary>
  378. /// <returns>The result of calling <see cref="System.Object.ToString"/> on the <see
  379. /// cref="Value"/>.</returns>
  380. /// <exception cref="T:System.NullReferenceException">
  381. /// The <see cref="Value"/> is null.
  382. /// </exception>
  383. public override string? ToString()
  384. {
  385. return IsValueCreated ?
  386. Value!.ToString() : // Throws NullReferenceException as if caller called ToString on the value itself
  387. SR.Lazy_ToString_ValueNotCreated;
  388. }
  389. /// <summary>Gets the value of the Lazy&lt;T&gt; for debugging display purposes.</summary>
  390. [MaybeNull]
  391. internal T ValueForDebugDisplay
  392. {
  393. get
  394. {
  395. if (!IsValueCreated)
  396. {
  397. return default!;
  398. }
  399. return _value;
  400. }
  401. }
  402. /// <summary>
  403. /// Gets a value indicating whether this instance may be used concurrently from multiple threads.
  404. /// </summary>
  405. internal LazyThreadSafetyMode? Mode => LazyHelper.GetMode(_state);
  406. /// <summary>
  407. /// Gets whether the value creation is faulted or not
  408. /// </summary>
  409. internal bool IsValueFaulted => LazyHelper.GetIsValueFaulted(_state);
  410. /// <summary>Gets a value indicating whether the <see cref="T:System.Lazy{T}"/> has been initialized.
  411. /// </summary>
  412. /// <value>true if the <see cref="T:System.Lazy{T}"/> instance has been initialized;
  413. /// otherwise, false.</value>
  414. /// <remarks>
  415. /// The initialization of a <see cref="T:System.Lazy{T}"/> instance may result in either
  416. /// a value being produced or an exception being thrown. If an exception goes unhandled during initialization,
  417. /// <see cref="IsValueCreated"/> will return false.
  418. /// </remarks>
  419. public bool IsValueCreated => _state == null;
  420. /// <summary>Gets the lazily initialized value of the current <see
  421. /// cref="T:System.Threading.Lazy{T}"/>.</summary>
  422. /// <value>The lazily initialized value of the current <see
  423. /// cref="T:System.Threading.Lazy{T}"/>.</value>
  424. /// <exception cref="T:System.MissingMemberException">
  425. /// The <see cref="T:System.Threading.Lazy{T}"/> was initialized to use the default constructor
  426. /// of the type being lazily initialized, and that type does not have a public, parameterless constructor.
  427. /// </exception>
  428. /// <exception cref="T:System.MemberAccessException">
  429. /// The <see cref="T:System.Threading.Lazy{T}"/> was initialized to use the default constructor
  430. /// of the type being lazily initialized, and permissions to access the constructor were missing.
  431. /// </exception>
  432. /// <exception cref="T:System.InvalidOperationException">
  433. /// The <see cref="T:System.Threading.Lazy{T}"/> was constructed with the <see cref="T:System.Threading.LazyThreadSafetyMode.ExecutionAndPublication"/> or
  434. /// <see cref="T:System.Threading.LazyThreadSafetyMode.None"/> and the initialization function attempted to access <see cref="Value"/> on this instance.
  435. /// </exception>
  436. /// <remarks>
  437. /// If <see cref="IsValueCreated"/> is false, accessing <see cref="Value"/> will force initialization.
  438. /// 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
  439. /// from initialization delegate.
  440. /// </remarks>
  441. [DebuggerBrowsable(DebuggerBrowsableState.Never)]
  442. public T Value => _state == null ? _value : CreateValue();
  443. }
  444. /// <summary>A debugger view of the Lazy&lt;T&gt; to surface additional debugging properties and
  445. /// to ensure that the Lazy&lt;T&gt; does not become initialized if it was not already.</summary>
  446. internal sealed class LazyDebugView<T>
  447. {
  448. //The Lazy object being viewed.
  449. private readonly Lazy<T> _lazy;
  450. /// <summary>Constructs a new debugger view object for the provided Lazy object.</summary>
  451. /// <param name="lazy">A Lazy object to browse in the debugger.</param>
  452. public LazyDebugView(Lazy<T> lazy)
  453. {
  454. _lazy = lazy;
  455. }
  456. /// <summary>Returns whether the Lazy object is initialized or not.</summary>
  457. public bool IsValueCreated
  458. {
  459. get { return _lazy.IsValueCreated; }
  460. }
  461. /// <summary>Returns the value of the Lazy object.</summary>
  462. public T Value
  463. {
  464. get { return _lazy.ValueForDebugDisplay; }
  465. }
  466. /// <summary>Returns the execution mode of the Lazy object</summary>
  467. public LazyThreadSafetyMode? Mode
  468. {
  469. get { return _lazy.Mode; }
  470. }
  471. /// <summary>Returns the execution mode of the Lazy object</summary>
  472. public bool IsValueFaulted
  473. {
  474. get { return _lazy.IsValueFaulted; }
  475. }
  476. }
  477. }