Lazy.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  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. #pragma warning disable 0420
  12. using System.Diagnostics;
  13. using System.Runtime.ExceptionServices;
  14. using System.Runtime.InteropServices;
  15. using System.Threading;
  16. namespace System
  17. {
  18. internal enum LazyState
  19. {
  20. NoneViaConstructor = 0,
  21. NoneViaFactory = 1,
  22. NoneException = 2,
  23. PublicationOnlyViaConstructor = 3,
  24. PublicationOnlyViaFactory = 4,
  25. PublicationOnlyWait = 5,
  26. PublicationOnlyException = 6,
  27. ExecutionAndPublicationViaConstructor = 7,
  28. ExecutionAndPublicationViaFactory = 8,
  29. ExecutionAndPublicationException = 9,
  30. }
  31. /// <summary>
  32. /// LazyHelper serves multiples purposes
  33. /// - minimizing code size of Lazy&lt;T&gt; by implementing as much of the code that is not generic
  34. /// this reduces generic code bloat, making faster class initialization
  35. /// - contains singleton objects that are used to handle threading primitives for PublicationOnly mode
  36. /// - allows for instantiation for ExecutionAndPublication so as to create an object for locking on
  37. /// - holds exception information.
  38. /// </summary>
  39. internal class LazyHelper
  40. {
  41. internal readonly static LazyHelper NoneViaConstructor = new LazyHelper(LazyState.NoneViaConstructor);
  42. internal readonly static LazyHelper NoneViaFactory = new LazyHelper(LazyState.NoneViaFactory);
  43. internal readonly static LazyHelper PublicationOnlyViaConstructor = new LazyHelper(LazyState.PublicationOnlyViaConstructor);
  44. internal readonly static LazyHelper PublicationOnlyViaFactory = new LazyHelper(LazyState.PublicationOnlyViaFactory);
  45. internal readonly static LazyHelper PublicationOnlyWaitForOtherThreadToPublish = new LazyHelper(LazyState.PublicationOnlyWait);
  46. internal LazyState State { get; }
  47. private readonly ExceptionDispatchInfo _exceptionDispatch;
  48. /// <summary>
  49. /// Constructor that defines the state
  50. /// </summary>
  51. internal LazyHelper(LazyState state)
  52. {
  53. State = state;
  54. }
  55. /// <summary>
  56. /// Constructor used for exceptions
  57. /// </summary>
  58. internal LazyHelper(LazyThreadSafetyMode mode, Exception exception)
  59. {
  60. switch(mode)
  61. {
  62. case LazyThreadSafetyMode.ExecutionAndPublication:
  63. State = LazyState.ExecutionAndPublicationException;
  64. break;
  65. case LazyThreadSafetyMode.None:
  66. State = LazyState.NoneException;
  67. break;
  68. case LazyThreadSafetyMode.PublicationOnly:
  69. State = LazyState.PublicationOnlyException;
  70. break;
  71. default:
  72. Debug.Fail("internal constructor, this should never occur");
  73. break;
  74. }
  75. _exceptionDispatch = ExceptionDispatchInfo.Capture(exception);
  76. }
  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 object CreateViaDefaultConstructor(Type type)
  128. {
  129. try
  130. {
  131. return Activator.CreateInstance(type);
  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()
  159. {
  160. return (T)LazyHelper.CreateViaDefaultConstructor(typeof(T));
  161. }
  162. // _state, a volatile reference, is set to null after _value has been set
  163. private volatile LazyHelper _state;
  164. // we ensure that _factory when finished is set to null to allow garbage collector to clean up
  165. // any referenced items
  166. private Func<T> _factory;
  167. // _value eventually stores the lazily created value. It is valid when _state = null.
  168. private T _value;
  169. /// <summary>
  170. /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class that
  171. /// uses <typeparamref name="T"/>'s default constructor for lazy initialization.
  172. /// </summary>
  173. /// <remarks>
  174. /// An instance created with this constructor may be used concurrently from multiple threads.
  175. /// </remarks>
  176. public Lazy()
  177. : this(null, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor:true)
  178. {
  179. }
  180. /// <summary>
  181. /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class that
  182. /// uses a pre-initialized specified value.
  183. /// </summary>
  184. /// <remarks>
  185. /// An instance created with this constructor should be usable by multiple threads
  186. /// concurrently.
  187. /// </remarks>
  188. public Lazy(T value)
  189. {
  190. _value = value;
  191. }
  192. /// <summary>
  193. /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class that uses a
  194. /// specified initialization function.
  195. /// </summary>
  196. /// <param name="valueFactory">
  197. /// The <see cref="T:System.Func{T}"/> invoked to produce the lazily-initialized value when it is
  198. /// needed.
  199. /// </param>
  200. /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is a null
  201. /// reference (Nothing in Visual Basic).</exception>
  202. /// <remarks>
  203. /// An instance created with this constructor may be used concurrently from multiple threads.
  204. /// </remarks>
  205. public Lazy(Func<T> valueFactory)
  206. : this(valueFactory, LazyThreadSafetyMode.ExecutionAndPublication, useDefaultConstructor:false)
  207. {
  208. }
  209. /// <summary>
  210. /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/>
  211. /// class that uses <typeparamref name="T"/>'s default constructor and a specified thread-safety mode.
  212. /// </summary>
  213. /// <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.
  214. /// </param>
  215. public Lazy(bool isThreadSafe) :
  216. this(null, LazyHelper.GetModeFromIsThreadSafe(isThreadSafe), useDefaultConstructor:true)
  217. {
  218. }
  219. /// <summary>
  220. /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/>
  221. /// class that uses <typeparamref name="T"/>'s default constructor and a specified thread-safety mode.
  222. /// </summary>
  223. /// <param name="mode">The lazy thread-safety mode</param>
  224. /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="mode"/> mode contains an invalid valuee</exception>
  225. public Lazy(LazyThreadSafetyMode mode) :
  226. this(null, mode, useDefaultConstructor:true)
  227. {
  228. }
  229. /// <summary>
  230. /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class
  231. /// that uses a specified initialization function and a specified thread-safety mode.
  232. /// </summary>
  233. /// <param name="valueFactory">
  234. /// The <see cref="T:System.Func{T}"/> invoked to produce the lazily-initialized value when it is needed.
  235. /// </param>
  236. /// <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.
  237. /// </param>
  238. /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is
  239. /// a null reference (Nothing in Visual Basic).</exception>
  240. public Lazy(Func<T> valueFactory, bool isThreadSafe) :
  241. this(valueFactory, LazyHelper.GetModeFromIsThreadSafe(isThreadSafe), useDefaultConstructor:false)
  242. {
  243. }
  244. /// <summary>
  245. /// Initializes a new instance of the <see cref="T:System.Threading.Lazy{T}"/> class
  246. /// that uses a specified initialization function and a specified thread-safety mode.
  247. /// </summary>
  248. /// <param name="valueFactory">
  249. /// The <see cref="T:System.Func{T}"/> invoked to produce the lazily-initialized value when it is needed.
  250. /// </param>
  251. /// <param name="mode">The lazy thread-safety mode.</param>
  252. /// <exception cref="System.ArgumentNullException"><paramref name="valueFactory"/> is
  253. /// a null reference (Nothing in Visual Basic).</exception>
  254. /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="mode"/> mode contains an invalid value.</exception>
  255. public Lazy(Func<T> valueFactory, LazyThreadSafetyMode mode)
  256. : this(valueFactory, mode, useDefaultConstructor:false)
  257. {
  258. }
  259. private Lazy(Func<T> valueFactory, LazyThreadSafetyMode mode, bool useDefaultConstructor)
  260. {
  261. if (valueFactory == null && !useDefaultConstructor)
  262. throw new ArgumentNullException(nameof(valueFactory));
  263. _factory = valueFactory;
  264. _state = LazyHelper.Create(mode, useDefaultConstructor);
  265. }
  266. private void ViaConstructor()
  267. {
  268. _value = CreateViaDefaultConstructor();
  269. _state = null; // volatile write, must occur after setting _value
  270. }
  271. private void ViaFactory(LazyThreadSafetyMode mode)
  272. {
  273. try
  274. {
  275. Func<T> factory = _factory;
  276. if (factory == null)
  277. throw new InvalidOperationException(SR.Lazy_Value_RecursiveCallsToValue);
  278. _factory = null;
  279. _value = factory();
  280. _state = null; // volatile write, must occur after setting _value
  281. }
  282. catch (Exception exception)
  283. {
  284. _state = new LazyHelper(mode, exception);
  285. throw;
  286. }
  287. }
  288. private void ExecutionAndPublication(LazyHelper executionAndPublication, bool useDefaultConstructor)
  289. {
  290. lock (executionAndPublication)
  291. {
  292. // it's possible for multiple calls to have piled up behind the lock, so we need to check
  293. // to see if the ExecutionAndPublication object is still the current implementation.
  294. if (ReferenceEquals(_state, executionAndPublication))
  295. {
  296. if (useDefaultConstructor)
  297. {
  298. ViaConstructor();
  299. }
  300. else
  301. {
  302. ViaFactory(LazyThreadSafetyMode.ExecutionAndPublication);
  303. }
  304. }
  305. }
  306. }
  307. private void PublicationOnly(LazyHelper publicationOnly, T possibleValue)
  308. {
  309. LazyHelper previous = Interlocked.CompareExchange(ref _state, LazyHelper.PublicationOnlyWaitForOtherThreadToPublish, publicationOnly);
  310. if (previous == publicationOnly)
  311. {
  312. _factory = null;
  313. _value = possibleValue;
  314. _state = null; // volatile write, must occur after setting _value
  315. }
  316. }
  317. private void PublicationOnlyViaConstructor(LazyHelper initializer)
  318. {
  319. PublicationOnly(initializer, CreateViaDefaultConstructor());
  320. }
  321. private void PublicationOnlyViaFactory(LazyHelper initializer)
  322. {
  323. Func<T> factory = _factory;
  324. if (factory == null)
  325. {
  326. PublicationOnlyWaitForOtherThreadToPublish();
  327. }
  328. else
  329. {
  330. PublicationOnly(initializer, factory());
  331. }
  332. }
  333. private void PublicationOnlyWaitForOtherThreadToPublish()
  334. {
  335. var spinWait = new SpinWait();
  336. while (!ReferenceEquals(_state, null))
  337. {
  338. // We get here when PublicationOnly temporarily sets _state to LazyHelper.PublicationOnlyWaitForOtherThreadToPublish.
  339. // This temporary state should be quickly followed by _state being set to null.
  340. spinWait.SpinOnce();
  341. }
  342. }
  343. private T CreateValue()
  344. {
  345. // we have to create a copy of state here, and use the copy exclusively from here on in
  346. // so as to ensure thread safety.
  347. var state = _state;
  348. if (state != null)
  349. {
  350. switch (state.State)
  351. {
  352. case LazyState.NoneViaConstructor:
  353. ViaConstructor();
  354. break;
  355. case LazyState.NoneViaFactory:
  356. ViaFactory(LazyThreadSafetyMode.None);
  357. break;
  358. case LazyState.PublicationOnlyViaConstructor:
  359. PublicationOnlyViaConstructor(state);
  360. break;
  361. case LazyState.PublicationOnlyViaFactory:
  362. PublicationOnlyViaFactory(state);
  363. break;
  364. case LazyState.PublicationOnlyWait:
  365. PublicationOnlyWaitForOtherThreadToPublish();
  366. break;
  367. case LazyState.ExecutionAndPublicationViaConstructor:
  368. ExecutionAndPublication(state, useDefaultConstructor:true);
  369. break;
  370. case LazyState.ExecutionAndPublicationViaFactory:
  371. ExecutionAndPublication(state, useDefaultConstructor:false);
  372. break;
  373. default:
  374. state.ThrowException();
  375. break;
  376. }
  377. }
  378. return Value;
  379. }
  380. /// <summary>Creates and returns a string representation of this instance.</summary>
  381. /// <returns>The result of calling <see cref="System.Object.ToString"/> on the <see
  382. /// cref="Value"/>.</returns>
  383. /// <exception cref="T:System.NullReferenceException">
  384. /// The <see cref="Value"/> is null.
  385. /// </exception>
  386. public override string ToString()
  387. {
  388. return IsValueCreated ? Value.ToString() : SR.Lazy_ToString_ValueNotCreated;
  389. }
  390. /// <summary>Gets the value of the Lazy&lt;T&gt; for debugging display purposes.</summary>
  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
  465. { return _lazy.ValueForDebugDisplay; }
  466. }
  467. /// <summary>Returns the execution mode of the Lazy object</summary>
  468. public LazyThreadSafetyMode? Mode
  469. {
  470. get { return _lazy.Mode; }
  471. }
  472. /// <summary>Returns the execution mode of the Lazy object</summary>
  473. public bool IsValueFaulted
  474. {
  475. get { return _lazy.IsValueFaulted; }
  476. }
  477. }
  478. }