AggregateException.cs 23 KB

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