3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
8 // AggregateException.cs
10 // <OWNER>Microsoft</OWNER>
12 // Public type to communicate multiple failures to an end-user.
14 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
17 using System.Collections.Generic;
18 using System.Collections.ObjectModel;
19 using System.Diagnostics;
20 using System.Globalization;
21 using System.Runtime.ExceptionServices;
22 using System.Runtime.Serialization;
23 using System.Security;
24 using System.Threading;
29 /// <summary>Represents one or more errors that occur during application execution.</summary>
31 /// <see cref="AggregateException"/> is used to consolidate multiple failures into a single, throwable
35 [DebuggerDisplay("Count = {InnerExceptionCount}")]
36 public class AggregateException : Exception
39 private ReadOnlyCollection<Exception> m_innerExceptions; // Complete set of exceptions.
42 /// Initializes a new instance of the <see cref="AggregateException"/> class.
44 public AggregateException()
45 : base(Environment.GetResourceString("AggregateException_ctor_DefaultMessage"))
47 m_innerExceptions = new ReadOnlyCollection<Exception>(new Exception[0]);
51 /// Initializes a new instance of the <see cref="AggregateException"/> class with
52 /// a specified error message.
54 /// <param name="message">The error message that explains the reason for the exception.</param>
55 public AggregateException(string message)
58 m_innerExceptions = new ReadOnlyCollection<Exception>(new Exception[0]);
62 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
63 /// message and a reference to the inner exception that is the cause of this exception.
65 /// <param name="message">The error message that explains the reason for the exception.</param>
66 /// <param name="innerException">The exception that is the cause of the current exception.</param>
67 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerException"/> argument
68 /// is null.</exception>
69 public AggregateException(string message, Exception innerException)
70 : base(message, innerException)
72 if (innerException == null)
74 throw new ArgumentNullException("innerException");
77 m_innerExceptions = new ReadOnlyCollection<Exception>(new Exception[] { innerException });
81 /// Initializes a new instance of the <see cref="AggregateException"/> class with
82 /// references to the inner exceptions that are the cause of this exception.
84 /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
85 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
86 /// is null.</exception>
87 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptions"/> is
89 public AggregateException(IEnumerable<Exception> innerExceptions) :
90 this(Environment.GetResourceString("AggregateException_ctor_DefaultMessage"), innerExceptions)
95 /// Initializes a new instance of the <see cref="AggregateException"/> class with
96 /// references to the inner exceptions that are the cause of this exception.
98 /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
99 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
100 /// is null.</exception>
101 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptions"/> is
102 /// null.</exception>
103 public AggregateException(params Exception[] innerExceptions) :
104 this(Environment.GetResourceString("AggregateException_ctor_DefaultMessage"), innerExceptions)
109 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
110 /// message and references to the inner exceptions that are the cause of this exception.
112 /// <param name="message">The error message that explains the reason for the exception.</param>
113 /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
114 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
115 /// is null.</exception>
116 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptions"/> is
117 /// null.</exception>
118 public AggregateException(string message, IEnumerable<Exception> innerExceptions)
119 // 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
120 // null typed correctly. Otherwise, create an IList from the enumerable and pass that along.
121 : this(message, innerExceptions as IList<Exception> ?? (innerExceptions == null ? (List<Exception>)null : new List<Exception>(innerExceptions)))
126 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
127 /// message and references to the inner exceptions that are the cause of this exception.
129 /// <param name="message">The error message that explains the reason for the exception.</param>
130 /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
131 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
132 /// is null.</exception>
133 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptions"/> is
134 /// null.</exception>
135 public AggregateException(string message, params Exception[] innerExceptions) :
136 this(message, (IList<Exception>)innerExceptions)
141 /// Allocates a new aggregate exception with the specified message and list of inner exceptions.
143 /// <param name="message">The error message that explains the reason for the exception.</param>
144 /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
145 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
146 /// is null.</exception>
147 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptions"/> is
148 /// null.</exception>
149 private AggregateException(string message, IList<Exception> innerExceptions)
150 : base(message, innerExceptions != null && innerExceptions.Count > 0 ? innerExceptions[0] : null)
152 if (innerExceptions == null)
154 throw new ArgumentNullException("innerExceptions");
157 // Copy exceptions to our internal array and validate them. We must copy them,
158 // because we're going to put them into a ReadOnlyCollection which simply reuses
159 // the list passed in to it. We don't want callers subsequently mutating.
160 Exception[] exceptionsCopy = new Exception[innerExceptions.Count];
162 for (int i = 0; i < exceptionsCopy.Length; i++)
164 exceptionsCopy[i] = innerExceptions[i];
166 if (exceptionsCopy[i] == null)
168 throw new ArgumentException(Environment.GetResourceString("AggregateException_ctor_InnerExceptionNull"));
172 m_innerExceptions = new ReadOnlyCollection<Exception>(exceptionsCopy);
176 /// Initializes a new instance of the <see cref="AggregateException"/> class with
177 /// references to the inner exception dispatch info objects that represent the cause of this exception.
179 /// <param name="innerExceptionInfos">
180 /// Information about the exceptions that are the cause of the current exception.
182 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument
183 /// is null.</exception>
184 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is
185 /// null.</exception>
186 internal AggregateException(IEnumerable<ExceptionDispatchInfo> innerExceptionInfos) :
187 this(Environment.GetResourceString("AggregateException_ctor_DefaultMessage"), innerExceptionInfos)
192 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
193 /// message and references to the inner exception dispatch info objects that represent the cause of
196 /// <param name="message">The error message that explains the reason for the exception.</param>
197 /// <param name="innerExceptionInfos">
198 /// Information about the exceptions that are the cause of the current exception.
200 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument
201 /// is null.</exception>
202 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is
203 /// null.</exception>
204 internal AggregateException(string message, IEnumerable<ExceptionDispatchInfo> innerExceptionInfos)
205 // 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
206 // null typed correctly. Otherwise, create an IList from the enumerable and pass that along.
207 : this(message, innerExceptionInfos as IList<ExceptionDispatchInfo> ??
208 (innerExceptionInfos == null ?
209 (List<ExceptionDispatchInfo>)null :
210 new List<ExceptionDispatchInfo>(innerExceptionInfos)))
215 /// Allocates a new aggregate exception with the specified message and list of inner
216 /// exception dispatch info objects.
218 /// <param name="message">The error message that explains the reason for the exception.</param>
219 /// <param name="innerExceptionInfos">
220 /// Information about the exceptions that are the cause of the current exception.
222 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument
223 /// is null.</exception>
224 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is
225 /// null.</exception>
226 private AggregateException(string message, IList<ExceptionDispatchInfo> innerExceptionInfos)
227 : base(message, innerExceptionInfos != null && innerExceptionInfos.Count > 0 && innerExceptionInfos[0] != null ?
228 innerExceptionInfos[0].SourceException : null)
230 if (innerExceptionInfos == null)
232 throw new ArgumentNullException("innerExceptionInfos");
235 // Copy exceptions to our internal array and validate them. We must copy them,
236 // because we're going to put them into a ReadOnlyCollection which simply reuses
237 // the list passed in to it. We don't want callers subsequently mutating.
238 Exception[] exceptionsCopy = new Exception[innerExceptionInfos.Count];
240 for (int i = 0; i < exceptionsCopy.Length; i++)
242 var edi = innerExceptionInfos[i];
243 if (edi != null) exceptionsCopy[i] = edi.SourceException;
245 if (exceptionsCopy[i] == null)
247 throw new ArgumentException(Environment.GetResourceString("AggregateException_ctor_InnerExceptionNull"));
251 m_innerExceptions = new ReadOnlyCollection<Exception>(exceptionsCopy);
255 /// Initializes a new instance of the <see cref="AggregateException"/> class with serialized data.
257 /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds
258 /// the serialized object data about the exception being thrown.</param>
259 /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that
260 /// contains contextual information about the source or destination. </param>
261 /// <exception cref="T:System.ArgumentNullException">The <paramref name="info"/> argument is null.</exception>
262 /// <exception cref="T:System.Runtime.Serialization.SerializationException">The exception could not be deserialized correctly.</exception>
264 protected AggregateException(SerializationInfo info, StreamingContext context) :
269 throw new ArgumentNullException("info");
272 Exception[] innerExceptions = info.GetValue("InnerExceptions", typeof(Exception[])) as Exception[];
273 if (innerExceptions == null)
275 throw new SerializationException(Environment.GetResourceString("AggregateException_DeserializationFailure"));
278 m_innerExceptions = new ReadOnlyCollection<Exception>(innerExceptions);
282 /// Sets the <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with information about
285 /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds
286 /// the serialized object data about the exception being thrown.</param>
287 /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that
288 /// contains contextual information about the source or destination. </param>
289 /// <exception cref="T:System.ArgumentNullException">The <paramref name="info"/> argument is null.</exception>
291 public override void GetObjectData(SerializationInfo info, StreamingContext context)
295 throw new ArgumentNullException("info");
298 base.GetObjectData(info, context);
300 Exception[] innerExceptions = new Exception[m_innerExceptions.Count];
301 m_innerExceptions.CopyTo(innerExceptions, 0);
302 info.AddValue("InnerExceptions", innerExceptions, typeof(Exception[]));
306 /// Returns the <see cref="System.AggregateException"/> that is the root cause of this exception.
308 public override Exception GetBaseException()
310 // Returns the first inner AggregateException that contains more or less than one inner exception
312 // Recursively traverse the inner exceptions as long as the inner exception of type AggregateException and has only one inner exception
313 Exception back = this;
314 AggregateException backAsAggregate = this;
315 while (backAsAggregate != null && backAsAggregate.InnerExceptions.Count == 1)
317 back = back.InnerException;
318 backAsAggregate = back as AggregateException;
324 /// Gets a read-only collection of the <see cref="T:System.Exception"/> instances that caused the
325 /// current exception.
327 public ReadOnlyCollection<Exception> InnerExceptions
329 get { return m_innerExceptions; }
334 /// Invokes a handler on each <see cref="T:System.Exception"/> contained by this <see
335 /// cref="AggregateException"/>.
337 /// <param name="predicate">The predicate to execute for each exception. The predicate accepts as an
338 /// argument the <see cref="T:System.Exception"/> to be processed and returns a Boolean to indicate
339 /// whether the exception was handled.</param>
341 /// Each invocation of the <paramref name="predicate"/> returns true or false to indicate whether the
342 /// <see cref="T:System.Exception"/> was handled. After all invocations, if any exceptions went
343 /// unhandled, all unhandled exceptions will be put into a new <see cref="AggregateException"/>
344 /// which will be thrown. Otherwise, the <see cref="Handle"/> method simply returns. If any
345 /// invocations of the <paramref name="predicate"/> throws an exception, it will halt the processing
346 /// of any more exceptions and immediately propagate the thrown exception as-is.
348 /// <exception cref="AggregateException">An exception contained by this <see
349 /// cref="AggregateException"/> was not handled.</exception>
350 /// <exception cref="T:System.ArgumentNullException">The <paramref name="predicate"/> argument is
351 /// null.</exception>
352 public void Handle(Func<Exception, bool> predicate)
354 if (predicate == null)
356 throw new ArgumentNullException("predicate");
359 List<Exception> unhandledExceptions = null;
360 for (int i = 0; i < m_innerExceptions.Count; i++)
362 // If the exception was not handled, lazily allocate a list of unhandled
363 // exceptions (to be rethrown later) and add it.
364 if (!predicate(m_innerExceptions[i]))
366 if (unhandledExceptions == null)
368 unhandledExceptions = new List<Exception>();
371 unhandledExceptions.Add(m_innerExceptions[i]);
375 // If there are unhandled exceptions remaining, throw them.
376 if (unhandledExceptions != null)
378 throw new AggregateException(Message, unhandledExceptions);
384 /// Flattens an <see cref="AggregateException"/> instances into a single, new instance.
386 /// <returns>A new, flattened <see cref="AggregateException"/>.</returns>
388 /// If any inner exceptions are themselves instances of
389 /// <see cref="AggregateException"/>, this method will recursively flatten all of them. The
390 /// inner exceptions returned in the new <see cref="AggregateException"/>
391 /// will be the union of all of the the inner exceptions from exception tree rooted at the provided
392 /// <see cref="AggregateException"/> instance.
394 public AggregateException Flatten()
396 // Initialize a collection to contain the flattened exceptions.
397 List<Exception> flattenedExceptions = new List<Exception>();
399 // Create a list to remember all aggregates to be flattened, this will be accessed like a FIFO queue
400 List<AggregateException> exceptionsToFlatten = new List<AggregateException>();
401 exceptionsToFlatten.Add(this);
402 int nDequeueIndex = 0;
404 // Continue removing and recursively flattening exceptions, until there are no more.
405 while (exceptionsToFlatten.Count > nDequeueIndex)
407 // dequeue one from exceptionsToFlatten
408 IList<Exception> currentInnerExceptions = exceptionsToFlatten[nDequeueIndex++].InnerExceptions;
410 for (int i = 0; i < currentInnerExceptions.Count; i++)
412 Exception currentInnerException = currentInnerExceptions[i];
414 if (currentInnerException == null)
419 AggregateException currentInnerAsAggregate = currentInnerException as AggregateException;
421 // If this exception is an aggregate, keep it around for later. Otherwise,
422 // simply add it to the list of flattened exceptions to be returned.
423 if (currentInnerAsAggregate != null)
425 exceptionsToFlatten.Add(currentInnerAsAggregate);
429 flattenedExceptions.Add(currentInnerException);
435 return new AggregateException(Message, flattenedExceptions);
439 /// Creates and returns a string representation of the current <see cref="AggregateException"/>.
441 /// <returns>A string representation of the current exception.</returns>
442 public override string ToString()
444 string text = base.ToString();
446 for (int i = 0; i < m_innerExceptions.Count; i++)
448 text = String.Format(
449 CultureInfo.InvariantCulture,
450 Environment.GetResourceString("AggregateException_ToString"),
451 text, Environment.NewLine, i, m_innerExceptions[i].ToString(), "<---", Environment.NewLine);
458 /// This helper property is used by the DebuggerDisplay.
460 /// Note that we don't want to remove this property and change the debugger display to {InnerExceptions.Count}
461 /// because DebuggerDisplay should be a single property access or parameterless method call, so that the debugger
462 /// can use a fast path without using the expression evaluator.
464 /// See http://msdn.microsoft.com/en-us/library/x810d419.aspx
466 private int InnerExceptionCount
470 return InnerExceptions.Count;