AggregateException.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  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. using System.Collections.Generic;
  5. using System.Collections.ObjectModel;
  6. using System.Diagnostics;
  7. using System.Globalization;
  8. using System.Runtime.ExceptionServices;
  9. using System.Runtime.Serialization;
  10. using System.Text;
  11. namespace System
  12. {
  13. /// <summary>Represents one or more errors that occur during application execution.</summary>
  14. /// <remarks>
  15. /// <see cref="AggregateException"/> is used to consolidate multiple failures into a single, throwable
  16. /// exception object.
  17. /// </remarks>
  18. [Serializable]
  19. [DebuggerDisplay("Count = {InnerExceptionCount}")]
  20. [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
  21. public class AggregateException : Exception
  22. {
  23. private readonly ReadOnlyCollection<Exception> m_innerExceptions; // Complete set of exceptions. Do not rename (binary serialization)
  24. /// <summary>
  25. /// Initializes a new instance of the <see cref="AggregateException"/> class.
  26. /// </summary>
  27. public AggregateException()
  28. : base(SR.AggregateException_ctor_DefaultMessage)
  29. {
  30. m_innerExceptions = new ReadOnlyCollection<Exception>(Array.Empty<Exception>());
  31. }
  32. /// <summary>
  33. /// Initializes a new instance of the <see cref="AggregateException"/> class with
  34. /// a specified error message.
  35. /// </summary>
  36. /// <param name="message">The error message that explains the reason for the exception.</param>
  37. public AggregateException(string? message)
  38. : base(message)
  39. {
  40. m_innerExceptions = new ReadOnlyCollection<Exception>(Array.Empty<Exception>());
  41. }
  42. /// <summary>
  43. /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
  44. /// message and a reference to the inner exception that is the cause of this exception.
  45. /// </summary>
  46. /// <param name="message">The error message that explains the reason for the exception.</param>
  47. /// <param name="innerException">The exception that is the cause of the current exception.</param>
  48. /// <exception cref="System.ArgumentNullException">The <paramref name="innerException"/> argument
  49. /// is null.</exception>
  50. public AggregateException(string? message, Exception innerException)
  51. : base(message, innerException)
  52. {
  53. if (innerException == null)
  54. {
  55. throw new ArgumentNullException(nameof(innerException));
  56. }
  57. m_innerExceptions = new ReadOnlyCollection<Exception>(new Exception[] { innerException });
  58. }
  59. /// <summary>
  60. /// Initializes a new instance of the <see cref="AggregateException"/> class with
  61. /// references to the inner exceptions that are the cause of this exception.
  62. /// </summary>
  63. /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
  64. /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
  65. /// is null.</exception>
  66. /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptions"/> is
  67. /// null.</exception>
  68. public AggregateException(IEnumerable<Exception> innerExceptions) :
  69. this(SR.AggregateException_ctor_DefaultMessage, innerExceptions)
  70. {
  71. }
  72. /// <summary>
  73. /// Initializes a new instance of the <see cref="AggregateException"/> class with
  74. /// references to the inner exceptions that are the cause of this exception.
  75. /// </summary>
  76. /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
  77. /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
  78. /// is null.</exception>
  79. /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptions"/> is
  80. /// null.</exception>
  81. public AggregateException(params Exception[] innerExceptions) :
  82. this(SR.AggregateException_ctor_DefaultMessage, innerExceptions)
  83. {
  84. }
  85. /// <summary>
  86. /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
  87. /// message and references to the inner exceptions that are the cause of this exception.
  88. /// </summary>
  89. /// <param name="message">The error message that explains the reason for the exception.</param>
  90. /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
  91. /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
  92. /// is null.</exception>
  93. /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptions"/> is
  94. /// null.</exception>
  95. public AggregateException(string? message, IEnumerable<Exception> innerExceptions)
  96. // If it's already an IList, pass that along (a defensive copy will be made in the delegated ctor). If it's null, just pass along
  97. // null typed correctly. Otherwise, create an IList from the enumerable and pass that along.
  98. : this(message, innerExceptions as IList<Exception> ?? (innerExceptions == null ? (List<Exception>)null! : new List<Exception>(innerExceptions)))
  99. {
  100. }
  101. /// <summary>
  102. /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
  103. /// message and references to the inner exceptions that are the cause of this exception.
  104. /// </summary>
  105. /// <param name="message">The error message that explains the reason for the exception.</param>
  106. /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
  107. /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
  108. /// is null.</exception>
  109. /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptions"/> is
  110. /// null.</exception>
  111. public AggregateException(string? message, params Exception[] innerExceptions) :
  112. this(message, (IList<Exception>)innerExceptions)
  113. {
  114. }
  115. /// <summary>
  116. /// Allocates a new aggregate exception with the specified message and list of inner exceptions.
  117. /// </summary>
  118. /// <param name="message">The error message that explains the reason for the exception.</param>
  119. /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
  120. /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
  121. /// is null.</exception>
  122. /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptions"/> is
  123. /// null.</exception>
  124. private AggregateException(string? message, IList<Exception> innerExceptions)
  125. : base(message, innerExceptions != null && innerExceptions.Count > 0 ? innerExceptions[0] : null)
  126. {
  127. if (innerExceptions == null)
  128. {
  129. throw new ArgumentNullException(nameof(innerExceptions));
  130. }
  131. // Copy exceptions to our internal array and validate them. We must copy them,
  132. // because we're going to put them into a ReadOnlyCollection which simply reuses
  133. // the list passed in to it. We don't want callers subsequently mutating.
  134. Exception[] exceptionsCopy = new Exception[innerExceptions.Count];
  135. for (int i = 0; i < exceptionsCopy.Length; i++)
  136. {
  137. exceptionsCopy[i] = innerExceptions[i];
  138. if (exceptionsCopy[i] == null)
  139. {
  140. throw new ArgumentException(SR.AggregateException_ctor_InnerExceptionNull);
  141. }
  142. }
  143. m_innerExceptions = new ReadOnlyCollection<Exception>(exceptionsCopy);
  144. }
  145. /// <summary>
  146. /// Initializes a new instance of the <see cref="AggregateException"/> class with
  147. /// references to the inner exception dispatch info objects that represent the cause of this exception.
  148. /// </summary>
  149. /// <param name="innerExceptionInfos">
  150. /// Information about the exceptions that are the cause of the current exception.
  151. /// </param>
  152. /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument
  153. /// is null.</exception>
  154. /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is
  155. /// null.</exception>
  156. internal AggregateException(IEnumerable<ExceptionDispatchInfo> innerExceptionInfos) :
  157. this(SR.AggregateException_ctor_DefaultMessage, innerExceptionInfos)
  158. {
  159. }
  160. /// <summary>
  161. /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
  162. /// message and references to the inner exception dispatch info objects that represent the cause of
  163. /// this exception.
  164. /// </summary>
  165. /// <param name="message">The error message that explains the reason for the exception.</param>
  166. /// <param name="innerExceptionInfos">
  167. /// Information about the exceptions that are the cause of the current exception.
  168. /// </param>
  169. /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument
  170. /// is null.</exception>
  171. /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is
  172. /// null.</exception>
  173. internal AggregateException(string message, IEnumerable<ExceptionDispatchInfo> innerExceptionInfos)
  174. // If it's already an IList, pass that along (a defensive copy will be made in the delegated ctor). If it's null, just pass along
  175. // null typed correctly. Otherwise, create an IList from the enumerable and pass that along.
  176. : this(message, innerExceptionInfos as IList<ExceptionDispatchInfo> ??
  177. (innerExceptionInfos == null ?
  178. (List<ExceptionDispatchInfo>)null! :
  179. new List<ExceptionDispatchInfo>(innerExceptionInfos)))
  180. {
  181. }
  182. /// <summary>
  183. /// Allocates a new aggregate exception with the specified message and list of inner
  184. /// exception dispatch info objects.
  185. /// </summary>
  186. /// <param name="message">The error message that explains the reason for the exception.</param>
  187. /// <param name="innerExceptionInfos">
  188. /// Information about the exceptions that are the cause of the current exception.
  189. /// </param>
  190. /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument
  191. /// is null.</exception>
  192. /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is
  193. /// null.</exception>
  194. private AggregateException(string message, IList<ExceptionDispatchInfo> innerExceptionInfos)
  195. : base(message, innerExceptionInfos != null && innerExceptionInfos.Count > 0 && innerExceptionInfos[0] != null ?
  196. innerExceptionInfos[0].SourceException : null)
  197. {
  198. if (innerExceptionInfos == null)
  199. {
  200. throw new ArgumentNullException(nameof(innerExceptionInfos));
  201. }
  202. // Copy exceptions to our internal array and validate them. We must copy them,
  203. // because we're going to put them into a ReadOnlyCollection which simply reuses
  204. // the list passed in to it. We don't want callers subsequently mutating.
  205. Exception[] exceptionsCopy = new Exception[innerExceptionInfos.Count];
  206. for (int i = 0; i < exceptionsCopy.Length; i++)
  207. {
  208. ExceptionDispatchInfo edi = innerExceptionInfos[i];
  209. if (edi != null) exceptionsCopy[i] = edi.SourceException;
  210. if (exceptionsCopy[i] == null)
  211. {
  212. throw new ArgumentException(SR.AggregateException_ctor_InnerExceptionNull);
  213. }
  214. }
  215. m_innerExceptions = new ReadOnlyCollection<Exception>(exceptionsCopy);
  216. }
  217. /// <summary>
  218. /// Initializes a new instance of the <see cref="AggregateException"/> class with serialized data.
  219. /// </summary>
  220. /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/> that holds
  221. /// the serialized object data about the exception being thrown.</param>
  222. /// <param name="context">The <see cref="System.Runtime.Serialization.StreamingContext"/> that
  223. /// contains contextual information about the source or destination. </param>
  224. /// <exception cref="System.ArgumentNullException">The <paramref name="info"/> argument is null.</exception>
  225. /// <exception cref="System.Runtime.Serialization.SerializationException">The exception could not be deserialized correctly.</exception>
  226. protected AggregateException(SerializationInfo info, StreamingContext context) :
  227. base(info, context)
  228. {
  229. if (info == null)
  230. {
  231. throw new ArgumentNullException(nameof(info));
  232. }
  233. Exception[]? innerExceptions = info.GetValue("InnerExceptions", typeof(Exception[])) as Exception[];
  234. if (innerExceptions is null)
  235. {
  236. throw new SerializationException(SR.AggregateException_DeserializationFailure);
  237. }
  238. m_innerExceptions = new ReadOnlyCollection<Exception>(innerExceptions);
  239. }
  240. /// <summary>
  241. /// Sets the <see cref="System.Runtime.Serialization.SerializationInfo"/> with information about
  242. /// the exception.
  243. /// </summary>
  244. /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/> that holds
  245. /// the serialized object data about the exception being thrown.</param>
  246. /// <param name="context">The <see cref="System.Runtime.Serialization.StreamingContext"/> that
  247. /// contains contextual information about the source or destination. </param>
  248. /// <exception cref="System.ArgumentNullException">The <paramref name="info"/> argument is null.</exception>
  249. public override void GetObjectData(SerializationInfo info, StreamingContext context)
  250. {
  251. base.GetObjectData(info, context);
  252. Exception[] innerExceptions = new Exception[m_innerExceptions.Count];
  253. m_innerExceptions.CopyTo(innerExceptions, 0);
  254. info.AddValue("InnerExceptions", innerExceptions, typeof(Exception[]));
  255. }
  256. /// <summary>
  257. /// Returns the <see cref="System.AggregateException"/> that is the root cause of this exception.
  258. /// </summary>
  259. public override Exception GetBaseException()
  260. {
  261. // Returns the first inner AggregateException that contains more or less than one inner exception
  262. // Recursively traverse the inner exceptions as long as the inner exception of type AggregateException and has only one inner exception
  263. Exception? back = this;
  264. AggregateException? backAsAggregate = this;
  265. while (backAsAggregate != null && backAsAggregate.InnerExceptions.Count == 1)
  266. {
  267. back = back!.InnerException;
  268. backAsAggregate = back as AggregateException;
  269. }
  270. return back!;
  271. }
  272. /// <summary>
  273. /// Gets a read-only collection of the <see cref="System.Exception"/> instances that caused the
  274. /// current exception.
  275. /// </summary>
  276. public ReadOnlyCollection<Exception> InnerExceptions => m_innerExceptions;
  277. /// <summary>
  278. /// Invokes a handler on each <see cref="System.Exception"/> contained by this <see
  279. /// cref="AggregateException"/>.
  280. /// </summary>
  281. /// <param name="predicate">The predicate to execute for each exception. The predicate accepts as an
  282. /// argument the <see cref="System.Exception"/> to be processed and returns a Boolean to indicate
  283. /// whether the exception was handled.</param>
  284. /// <remarks>
  285. /// Each invocation of the <paramref name="predicate"/> returns true or false to indicate whether the
  286. /// <see cref="System.Exception"/> was handled. After all invocations, if any exceptions went
  287. /// unhandled, all unhandled exceptions will be put into a new <see cref="AggregateException"/>
  288. /// which will be thrown. Otherwise, the <see cref="Handle"/> method simply returns. If any
  289. /// invocations of the <paramref name="predicate"/> throws an exception, it will halt the processing
  290. /// of any more exceptions and immediately propagate the thrown exception as-is.
  291. /// </remarks>
  292. /// <exception cref="AggregateException">An exception contained by this <see
  293. /// cref="AggregateException"/> was not handled.</exception>
  294. /// <exception cref="System.ArgumentNullException">The <paramref name="predicate"/> argument is
  295. /// null.</exception>
  296. public void Handle(Func<Exception, bool> predicate)
  297. {
  298. if (predicate == null)
  299. {
  300. throw new ArgumentNullException(nameof(predicate));
  301. }
  302. List<Exception>? unhandledExceptions = null;
  303. for (int i = 0; i < m_innerExceptions.Count; i++)
  304. {
  305. // If the exception was not handled, lazily allocate a list of unhandled
  306. // exceptions (to be rethrown later) and add it.
  307. if (!predicate(m_innerExceptions[i]))
  308. {
  309. unhandledExceptions ??= new List<Exception>();
  310. unhandledExceptions.Add(m_innerExceptions[i]);
  311. }
  312. }
  313. // If there are unhandled exceptions remaining, throw them.
  314. if (unhandledExceptions != null)
  315. {
  316. throw new AggregateException(Message, unhandledExceptions);
  317. }
  318. }
  319. /// <summary>
  320. /// Flattens the inner instances of <see cref="AggregateException"/> by expanding its contained <see cref="Exception"/> instances
  321. /// into a new <see cref="AggregateException"/>
  322. /// </summary>
  323. /// <returns>A new, flattened <see cref="AggregateException"/>.</returns>
  324. /// <remarks>
  325. /// If any inner exceptions are themselves instances of
  326. /// <see cref="AggregateException"/>, this method will recursively flatten all of them. The
  327. /// inner exceptions returned in the new <see cref="AggregateException"/>
  328. /// will be the union of all of the inner exceptions from exception tree rooted at the provided
  329. /// <see cref="AggregateException"/> instance.
  330. /// </remarks>
  331. public AggregateException Flatten()
  332. {
  333. // Initialize a collection to contain the flattened exceptions.
  334. List<Exception> flattenedExceptions = new List<Exception>();
  335. // Create a list to remember all aggregates to be flattened, this will be accessed like a FIFO queue
  336. var exceptionsToFlatten = new List<AggregateException> { this };
  337. int nDequeueIndex = 0;
  338. // Continue removing and recursively flattening exceptions, until there are no more.
  339. while (exceptionsToFlatten.Count > nDequeueIndex)
  340. {
  341. // dequeue one from exceptionsToFlatten
  342. IList<Exception> currentInnerExceptions = exceptionsToFlatten[nDequeueIndex++].InnerExceptions;
  343. for (int i = 0; i < currentInnerExceptions.Count; i++)
  344. {
  345. Exception currentInnerException = currentInnerExceptions[i];
  346. if (currentInnerException == null)
  347. {
  348. continue;
  349. }
  350. // If this exception is an aggregate, keep it around for later. Otherwise,
  351. // simply add it to the list of flattened exceptions to be returned.
  352. if (currentInnerException is AggregateException currentInnerAsAggregate)
  353. {
  354. exceptionsToFlatten.Add(currentInnerAsAggregate);
  355. }
  356. else
  357. {
  358. flattenedExceptions.Add(currentInnerException);
  359. }
  360. }
  361. }
  362. return new AggregateException(Message, flattenedExceptions);
  363. }
  364. /// <summary>Gets a message that describes the exception.</summary>
  365. public override string Message
  366. {
  367. get
  368. {
  369. if (m_innerExceptions.Count == 0)
  370. {
  371. return base.Message;
  372. }
  373. StringBuilder sb = StringBuilderCache.Acquire();
  374. sb.Append(base.Message);
  375. sb.Append(' ');
  376. for (int i = 0; i < m_innerExceptions.Count; i++)
  377. {
  378. sb.Append('(');
  379. sb.Append(m_innerExceptions[i].Message);
  380. sb.Append(") ");
  381. }
  382. sb.Length--;
  383. return StringBuilderCache.GetStringAndRelease(sb);
  384. }
  385. }
  386. /// <summary>
  387. /// Creates and returns a string representation of the current <see cref="AggregateException"/>.
  388. /// </summary>
  389. /// <returns>A string representation of the current exception.</returns>
  390. public override string ToString()
  391. {
  392. StringBuilder text = new StringBuilder();
  393. text.Append(base.ToString());
  394. for (int i = 0; i < m_innerExceptions.Count; i++)
  395. {
  396. if (m_innerExceptions[i] == InnerException)
  397. continue; // Already logged in base.ToString()
  398. text.Append(Environment.NewLineConst + InnerExceptionPrefix);
  399. text.AppendFormat(CultureInfo.InvariantCulture, SR.AggregateException_InnerException, i);
  400. text.Append(m_innerExceptions[i].ToString());
  401. text.Append("<---");
  402. text.AppendLine();
  403. }
  404. return text.ToString();
  405. }
  406. /// <summary>
  407. /// This helper property is used by the DebuggerDisplay.
  408. ///
  409. /// Note that we don't want to remove this property and change the debugger display to {InnerExceptions.Count}
  410. /// because DebuggerDisplay should be a single property access or parameterless method call, so that the debugger
  411. /// can use a fast path without using the expression evaluator.
  412. ///
  413. /// See https://docs.microsoft.com/en-us/visualstudio/debugger/using-the-debuggerdisplay-attribute
  414. /// </summary>
  415. private int InnerExceptionCount => InnerExceptions.Count;
  416. }
  417. }