Lazy.cs 22 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.Runtime.ExceptionServices;
  13. using System.Runtime.InteropServices;
  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 readonly static LazyHelper NoneViaConstructor = new LazyHelper(LazyState.NoneViaConstructor);
  41. internal readonly static LazyHelper NoneViaFactory = new LazyHelper(LazyState.NoneViaFactory);
  42. internal readonly static LazyHelper PublicationOnlyViaConstructor = new LazyHelper(LazyState.PublicationOnlyViaConstructor);
  43. internal readonly static LazyHelper PublicationOnlyViaFactory = new LazyHelper(LazyState.PublicationOnlyViaFactory);
  44. internal readonly static 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. internal void ThrowException()
  77. {
  78. Debug.Assert(_exceptionDispatch != null, "execution path is invalid");
  79. _exceptionDispatch.Throw();
  80. }
  81. private LazyThreadSafetyMode GetMode()
  82. {
  83. switch (State)
  84. {
  85. case LazyState.NoneViaConstructor:
  86. case LazyState.NoneViaFactory:
  87. case LazyState.NoneException:
  88. return LazyThreadSafetyMode.None;
  89. case LazyState.PublicationOnlyViaConstructor:
  90. case LazyState.PublicationOnlyViaFactory:
  91. case LazyState.PublicationOnlyWait:
  92. case LazyState.PublicationOnlyException:
  93. return LazyThreadSafetyMode.PublicationOnly;
  94. case LazyState.ExecutionAndPublicationViaConstructor:
  95. case LazyState.ExecutionAndPublicationViaFactory:
  96. case LazyState.ExecutionAndPublicationException:
  97. return LazyThreadSafetyMode.ExecutionAndPublication;
  98. default:
  99. Debug.Fail("Invalid logic; State should always have a valid value");
  100. return default;
  101. }
  102. }
  103. internal static LazyThreadSafetyMode? GetMode(LazyHelper state)
  104. {
  105. if (state == null)
  106. return null; // we don't know the mode anymore
  107. return state.GetMode();
  108. }
  109. internal static bool GetIsValueFaulted(LazyHelper state) => state?._exceptionDispatch != null;
  110. internal static LazyHelper Create(LazyThreadSafetyMode mode, bool useDefaultConstructor)
  111. {
  112. switch (mode)
  113. {
  114. case LazyThreadSafetyMode.None:
  115. return useDefaultConstructor ? NoneViaConstructor : NoneViaFactory;
  116. case LazyThreadSafetyMode.PublicationOnly:
  117. return useDefaultConstructor ? PublicationOnlyViaConstructor : PublicationOnlyViaFactory;
  118. case LazyThreadSafetyMode.ExecutionAndPublication:
  119. // we need to create an object for ExecutionAndPublication because we use Monitor-based locking
  120. var state = useDefaultConstructor ? LazyState.ExecutionAndPublicationViaConstructor : LazyState.ExecutionAndPublicationViaFactory;
  121. return new LazyHelper(state);
  122. default:
  123. throw new ArgumentOutOfRangeException(nameof(mode), SR.Lazy_ctor_ModeInvalid);
  124. }
  125. }
  126. internal static object CreateViaDefaultConstructor(Type type)
  127. {
  128. try
  129. {
  130. return Activator.CreateInstance(type);
  131. }
  132. catch (MissingMethodException)
  133. {
  134. throw new MissingMemberException(SR.Lazy_CreateValue_NoParameterlessCtorForT);
  135. }
  136. }
  137. internal static LazyThreadSafetyMode GetModeFromIsThreadSafe(bool isThreadSafe)
  138. {
  139. return isThreadSafe ? LazyThreadSafetyMode.ExecutionAndPublication : LazyThreadSafetyMode.None;
  140. }
  141. }
  142. /// <summary>
  143. /// Provides support for lazy initialization.
  144. /// </summary>
  145. /// <typeparam name="T">Specifies the type of element being lazily initialized.</typeparam>
  146. /// <remarks>
  147. /// <para>
  148. /// By default, all public and protected members of <see cref="Lazy{T}"/> are thread-safe and may be used
  149. /// concurrently from multiple threads. These thread-safety guarantees may be removed optionally and per instance
  150. /// using parameters to the type's constructors.
  151. /// </para>
  152. /// </remarks>
  153. [DebuggerTypeProxy(typeof(LazyDebugView<>))]
  154. [DebuggerDisplay("ThreadSafetyMode={Mode}, IsValueCreated={IsValueCreated}, IsValueFaulted={IsValueFaulted}, Value={ValueForDebugDisplay}")]
  155. public class Lazy<T>
  156. {
  157. private static T CreateViaDefaultConstructor()
  158. {
  159. return (T)LazyHelper.CreateViaDefaultConstructor(typeof(T));
  160. }
  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;
  168. /// <summary>
  169. /// Initializes a new instance of the <see cref="T:System.Threading.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="T:System.Threading.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="T:System.Threading.Lazy{T}"/> class that uses a
  193. /// specified initialization function.
  194. /// </summary>
  195. /// <param name="valueFactory">
  196. /// The <see cref="T: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="T:System.Threading.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="T:System.Threading.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="T:System.Threading.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="T: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="T:System.Threading.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="T: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. var spinWait = new SpinWait();
  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. var 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="System.Object.ToString"/> on the <see
  381. /// cref="Value"/>.</returns>
  382. /// <exception cref="T:System.NullReferenceException">
  383. /// The <see cref="Value"/> is null.
  384. /// </exception>
  385. public override string ToString()
  386. {
  387. return IsValueCreated ? Value.ToString() : SR.Lazy_ToString_ValueNotCreated;
  388. }
  389. /// <summary>Gets the value of the Lazy&lt;T&gt; for debugging display purposes.</summary>
  390. internal T ValueForDebugDisplay
  391. {
  392. get
  393. {
  394. if (!IsValueCreated)
  395. {
  396. return default;
  397. }
  398. return _value;
  399. }
  400. }
  401. /// <summary>
  402. /// Gets a value indicating whether this instance may be used concurrently from multiple threads.
  403. /// </summary>
  404. internal LazyThreadSafetyMode? Mode => LazyHelper.GetMode(_state);
  405. /// <summary>
  406. /// Gets whether the value creation is faulted or not
  407. /// </summary>
  408. internal bool IsValueFaulted => LazyHelper.GetIsValueFaulted(_state);
  409. /// <summary>Gets a value indicating whether the <see cref="T:System.Lazy{T}"/> has been initialized.
  410. /// </summary>
  411. /// <value>true if the <see cref="T:System.Lazy{T}"/> instance has been initialized;
  412. /// otherwise, false.</value>
  413. /// <remarks>
  414. /// The initialization of a <see cref="T:System.Lazy{T}"/> instance may result in either
  415. /// a value being produced or an exception being thrown. If an exception goes unhandled during initialization,
  416. /// <see cref="IsValueCreated"/> will return false.
  417. /// </remarks>
  418. public bool IsValueCreated => _state == null;
  419. /// <summary>Gets the lazily initialized value of the current <see
  420. /// cref="T:System.Threading.Lazy{T}"/>.</summary>
  421. /// <value>The lazily initialized value of the current <see
  422. /// cref="T:System.Threading.Lazy{T}"/>.</value>
  423. /// <exception cref="T:System.MissingMemberException">
  424. /// The <see cref="T:System.Threading.Lazy{T}"/> was initialized to use the default constructor
  425. /// of the type being lazily initialized, and that type does not have a public, parameterless constructor.
  426. /// </exception>
  427. /// <exception cref="T:System.MemberAccessException">
  428. /// The <see cref="T:System.Threading.Lazy{T}"/> was initialized to use the default constructor
  429. /// of the type being lazily initialized, and permissions to access the constructor were missing.
  430. /// </exception>
  431. /// <exception cref="T:System.InvalidOperationException">
  432. /// The <see cref="T:System.Threading.Lazy{T}"/> was constructed with the <see cref="T:System.Threading.LazyThreadSafetyMode.ExecutionAndPublication"/> or
  433. /// <see cref="T:System.Threading.LazyThreadSafetyMode.None"/> and the initialization function attempted to access <see cref="Value"/> on this instance.
  434. /// </exception>
  435. /// <remarks>
  436. /// If <see cref="IsValueCreated"/> is false, accessing <see cref="Value"/> will force initialization.
  437. /// 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
  438. /// from initialization delegate.
  439. /// </remarks>
  440. [DebuggerBrowsable(DebuggerBrowsableState.Never)]
  441. public T Value => _state == null ? _value : CreateValue();
  442. }
  443. /// <summary>A debugger view of the Lazy&lt;T&gt; to surface additional debugging properties and
  444. /// to ensure that the Lazy&lt;T&gt; does not become initialized if it was not already.</summary>
  445. internal sealed class LazyDebugView<T>
  446. {
  447. //The Lazy object being viewed.
  448. private readonly Lazy<T> _lazy;
  449. /// <summary>Constructs a new debugger view object for the provided Lazy object.</summary>
  450. /// <param name="lazy">A Lazy object to browse in the debugger.</param>
  451. public LazyDebugView(Lazy<T> lazy)
  452. {
  453. _lazy = lazy;
  454. }
  455. /// <summary>Returns whether the Lazy object is initialized or not.</summary>
  456. public bool IsValueCreated
  457. {
  458. get { return _lazy.IsValueCreated; }
  459. }
  460. /// <summary>Returns the value of the Lazy object.</summary>
  461. public T Value
  462. {
  463. get
  464. { 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. }