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.
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
;
15 /// <summary>Represents one or more errors that occur during application execution.</summary>
17 /// <see cref="AggregateException"/> is used to consolidate multiple failures into a single, throwable
21 [DebuggerDisplay("Count = {InnerExceptionCount}")]
22 [System
.Runtime
.CompilerServices
.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
23 public class AggregateException
: Exception
25 private readonly ReadOnlyCollection
<Exception
> m_innerExceptions
; // Complete set of exceptions. Do not rename (binary serialization)
28 /// Initializes a new instance of the <see cref="AggregateException"/> class.
30 public AggregateException()
31 : base(SR
.AggregateException_ctor_DefaultMessage
)
33 m_innerExceptions
= new ReadOnlyCollection
<Exception
>(Array
.Empty
<Exception
>());
37 /// Initializes a new instance of the <see cref="AggregateException"/> class with
38 /// a specified error message.
40 /// <param name="message">The error message that explains the reason for the exception.</param>
41 public AggregateException(string? message
)
44 m_innerExceptions
= new ReadOnlyCollection
<Exception
>(Array
.Empty
<Exception
>());
48 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
49 /// message and a reference to the inner exception that is the cause of this exception.
51 /// <param name="message">The error message that explains the reason for the exception.</param>
52 /// <param name="innerException">The exception that is the cause of the current exception.</param>
53 /// <exception cref="System.ArgumentNullException">The <paramref name="innerException"/> argument
54 /// is null.</exception>
55 public AggregateException(string? message
, Exception innerException
)
56 : base(message
, innerException
)
58 if (innerException
== null)
60 throw new ArgumentNullException(nameof(innerException
));
63 m_innerExceptions
= new ReadOnlyCollection
<Exception
>(new Exception
[] { innerException }
);
67 /// Initializes a new instance of the <see cref="AggregateException"/> class with
68 /// references to the inner exceptions that are the cause of this exception.
70 /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
71 /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
72 /// is null.</exception>
73 /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptions"/> is
75 public AggregateException(IEnumerable
<Exception
> innerExceptions
) :
76 this(SR
.AggregateException_ctor_DefaultMessage
, innerExceptions
)
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="System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
86 /// is null.</exception>
87 /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptions"/> is
89 public AggregateException(params Exception
[] innerExceptions
) :
90 this(SR
.AggregateException_ctor_DefaultMessage
, innerExceptions
)
95 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
96 /// message and references to the inner exceptions that are the cause of this exception.
98 /// <param name="message">The error message that explains the reason for the exception.</param>
99 /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
100 /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
101 /// is null.</exception>
102 /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptions"/> is
103 /// null.</exception>
104 public AggregateException(string? message
, IEnumerable
<Exception
> innerExceptions
)
105 // 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
106 // null typed correctly. Otherwise, create an IList from the enumerable and pass that along.
107 : this(message
, innerExceptions
as IList
<Exception
> ?? (innerExceptions
== null ? (List
<Exception
>)null! : new List
<Exception
>(innerExceptions
)))
112 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
113 /// message and references to the inner exceptions that are the cause of this exception.
115 /// <param name="message">The error message that explains the reason for the exception.</param>
116 /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
117 /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
118 /// is null.</exception>
119 /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptions"/> is
120 /// null.</exception>
121 public AggregateException(string? message
, params Exception
[] innerExceptions
) :
122 this(message
, (IList
<Exception
>)innerExceptions
)
127 /// Allocates a new aggregate exception with the specified message and list of inner exceptions.
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="System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
132 /// is null.</exception>
133 /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptions"/> is
134 /// null.</exception>
135 private AggregateException(string? message
, IList
<Exception
> innerExceptions
)
136 : base(message
, innerExceptions
!= null && innerExceptions
.Count
> 0 ? innerExceptions
[0] : null)
138 if (innerExceptions
== null)
140 throw new ArgumentNullException(nameof(innerExceptions
));
143 // Copy exceptions to our internal array and validate them. We must copy them,
144 // because we're going to put them into a ReadOnlyCollection which simply reuses
145 // the list passed in to it. We don't want callers subsequently mutating.
146 Exception
[] exceptionsCopy
= new Exception
[innerExceptions
.Count
];
148 for (int i
= 0; i
< exceptionsCopy
.Length
; i
++)
150 exceptionsCopy
[i
] = innerExceptions
[i
];
152 if (exceptionsCopy
[i
] == null)
154 throw new ArgumentException(SR
.AggregateException_ctor_InnerExceptionNull
);
158 m_innerExceptions
= new ReadOnlyCollection
<Exception
>(exceptionsCopy
);
162 /// Initializes a new instance of the <see cref="AggregateException"/> class with
163 /// references to the inner exception dispatch info objects that represent the cause of this exception.
165 /// <param name="innerExceptionInfos">
166 /// Information about the exceptions that are the cause of the current exception.
168 /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument
169 /// is null.</exception>
170 /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is
171 /// null.</exception>
172 internal AggregateException(IEnumerable
<ExceptionDispatchInfo
> innerExceptionInfos
) :
173 this(SR
.AggregateException_ctor_DefaultMessage
, innerExceptionInfos
)
178 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
179 /// message and references to the inner exception dispatch info objects that represent the cause of
182 /// <param name="message">The error message that explains the reason for the exception.</param>
183 /// <param name="innerExceptionInfos">
184 /// Information about the exceptions that are the cause of the current exception.
186 /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument
187 /// is null.</exception>
188 /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is
189 /// null.</exception>
190 internal AggregateException(string message
, IEnumerable
<ExceptionDispatchInfo
> innerExceptionInfos
)
191 // 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
192 // null typed correctly. Otherwise, create an IList from the enumerable and pass that along.
193 : this(message
, innerExceptionInfos
as IList
<ExceptionDispatchInfo
> ??
194 (innerExceptionInfos
== null ?
195 (List
<ExceptionDispatchInfo
>)null! :
196 new List
<ExceptionDispatchInfo
>(innerExceptionInfos
)))
201 /// Allocates a new aggregate exception with the specified message and list of inner
202 /// exception dispatch info objects.
204 /// <param name="message">The error message that explains the reason for the exception.</param>
205 /// <param name="innerExceptionInfos">
206 /// Information about the exceptions that are the cause of the current exception.
208 /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument
209 /// is null.</exception>
210 /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is
211 /// null.</exception>
212 private AggregateException(string message
, IList
<ExceptionDispatchInfo
> innerExceptionInfos
)
213 : base(message
, innerExceptionInfos
!= null && innerExceptionInfos
.Count
> 0 && innerExceptionInfos
[0] != null ?
214 innerExceptionInfos
[0].SourceException
: null)
216 if (innerExceptionInfos
== null)
218 throw new ArgumentNullException(nameof(innerExceptionInfos
));
221 // Copy exceptions to our internal array and validate them. We must copy them,
222 // because we're going to put them into a ReadOnlyCollection which simply reuses
223 // the list passed in to it. We don't want callers subsequently mutating.
224 Exception
[] exceptionsCopy
= new Exception
[innerExceptionInfos
.Count
];
226 for (int i
= 0; i
< exceptionsCopy
.Length
; i
++)
228 ExceptionDispatchInfo edi
= innerExceptionInfos
[i
];
229 if (edi
!= null) exceptionsCopy
[i
] = edi
.SourceException
;
231 if (exceptionsCopy
[i
] == null)
233 throw new ArgumentException(SR
.AggregateException_ctor_InnerExceptionNull
);
237 m_innerExceptions
= new ReadOnlyCollection
<Exception
>(exceptionsCopy
);
241 /// Initializes a new instance of the <see cref="AggregateException"/> class with serialized data.
243 /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/> that holds
244 /// the serialized object data about the exception being thrown.</param>
245 /// <param name="context">The <see cref="System.Runtime.Serialization.StreamingContext"/> that
246 /// contains contextual information about the source or destination. </param>
247 /// <exception cref="System.ArgumentNullException">The <paramref name="info"/> argument is null.</exception>
248 /// <exception cref="System.Runtime.Serialization.SerializationException">The exception could not be deserialized correctly.</exception>
249 protected AggregateException(SerializationInfo info
, StreamingContext context
) :
254 throw new ArgumentNullException(nameof(info
));
257 Exception
[]? innerExceptions
= info
.GetValue("InnerExceptions", typeof(Exception
[])) as Exception
[];
258 if (innerExceptions
is null)
260 throw new SerializationException(SR
.AggregateException_DeserializationFailure
);
263 m_innerExceptions
= new ReadOnlyCollection
<Exception
>(innerExceptions
);
267 /// Sets the <see cref="System.Runtime.Serialization.SerializationInfo"/> with information about
270 /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/> that holds
271 /// the serialized object data about the exception being thrown.</param>
272 /// <param name="context">The <see cref="System.Runtime.Serialization.StreamingContext"/> that
273 /// contains contextual information about the source or destination. </param>
274 /// <exception cref="System.ArgumentNullException">The <paramref name="info"/> argument is null.</exception>
275 public override void GetObjectData(SerializationInfo info
, StreamingContext context
)
277 base.GetObjectData(info
, context
);
279 Exception
[] innerExceptions
= new Exception
[m_innerExceptions
.Count
];
280 m_innerExceptions
.CopyTo(innerExceptions
, 0);
281 info
.AddValue("InnerExceptions", innerExceptions
, typeof(Exception
[]));
285 /// Returns the <see cref="System.AggregateException"/> that is the root cause of this exception.
287 public override Exception
GetBaseException()
289 // Returns the first inner AggregateException that contains more or less than one inner exception
291 // Recursively traverse the inner exceptions as long as the inner exception of type AggregateException and has only one inner exception
292 Exception
? back
= this;
293 AggregateException
? backAsAggregate
= this;
294 while (backAsAggregate
!= null && backAsAggregate
.InnerExceptions
.Count
== 1)
296 back
= back
!.InnerException
;
297 backAsAggregate
= back
as AggregateException
;
303 /// Gets a read-only collection of the <see cref="System.Exception"/> instances that caused the
304 /// current exception.
306 public ReadOnlyCollection
<Exception
> InnerExceptions
=> m_innerExceptions
;
310 /// Invokes a handler on each <see cref="System.Exception"/> contained by this <see
311 /// cref="AggregateException"/>.
313 /// <param name="predicate">The predicate to execute for each exception. The predicate accepts as an
314 /// argument the <see cref="System.Exception"/> to be processed and returns a Boolean to indicate
315 /// whether the exception was handled.</param>
317 /// Each invocation of the <paramref name="predicate"/> returns true or false to indicate whether the
318 /// <see cref="System.Exception"/> was handled. After all invocations, if any exceptions went
319 /// unhandled, all unhandled exceptions will be put into a new <see cref="AggregateException"/>
320 /// which will be thrown. Otherwise, the <see cref="Handle"/> method simply returns. If any
321 /// invocations of the <paramref name="predicate"/> throws an exception, it will halt the processing
322 /// of any more exceptions and immediately propagate the thrown exception as-is.
324 /// <exception cref="AggregateException">An exception contained by this <see
325 /// cref="AggregateException"/> was not handled.</exception>
326 /// <exception cref="System.ArgumentNullException">The <paramref name="predicate"/> argument is
327 /// null.</exception>
328 public void Handle(Func
<Exception
, bool> predicate
)
330 if (predicate
== null)
332 throw new ArgumentNullException(nameof(predicate
));
335 List
<Exception
>? unhandledExceptions
= null;
336 for (int i
= 0; i
< m_innerExceptions
.Count
; i
++)
338 // If the exception was not handled, lazily allocate a list of unhandled
339 // exceptions (to be rethrown later) and add it.
340 if (!predicate(m_innerExceptions
[i
]))
342 unhandledExceptions
??= new List
<Exception
>();
343 unhandledExceptions
.Add(m_innerExceptions
[i
]);
347 // If there are unhandled exceptions remaining, throw them.
348 if (unhandledExceptions
!= null)
350 throw new AggregateException(Message
, unhandledExceptions
);
356 /// Flattens the inner instances of <see cref="AggregateException"/> by expanding its contained <see cref="Exception"/> instances
357 /// into a new <see cref="AggregateException"/>
359 /// <returns>A new, flattened <see cref="AggregateException"/>.</returns>
361 /// If any inner exceptions are themselves instances of
362 /// <see cref="AggregateException"/>, this method will recursively flatten all of them. The
363 /// inner exceptions returned in the new <see cref="AggregateException"/>
364 /// will be the union of all of the inner exceptions from exception tree rooted at the provided
365 /// <see cref="AggregateException"/> instance.
367 public AggregateException
Flatten()
369 // Initialize a collection to contain the flattened exceptions.
370 List
<Exception
> flattenedExceptions
= new List
<Exception
>();
372 // Create a list to remember all aggregates to be flattened, this will be accessed like a FIFO queue
373 var exceptionsToFlatten
= new List
<AggregateException
> { this }
;
374 int nDequeueIndex
= 0;
376 // Continue removing and recursively flattening exceptions, until there are no more.
377 while (exceptionsToFlatten
.Count
> nDequeueIndex
)
379 // dequeue one from exceptionsToFlatten
380 IList
<Exception
> currentInnerExceptions
= exceptionsToFlatten
[nDequeueIndex
++].InnerExceptions
;
382 for (int i
= 0; i
< currentInnerExceptions
.Count
; i
++)
384 Exception currentInnerException
= currentInnerExceptions
[i
];
386 if (currentInnerException
== null)
391 // If this exception is an aggregate, keep it around for later. Otherwise,
392 // simply add it to the list of flattened exceptions to be returned.
393 if (currentInnerException
is AggregateException currentInnerAsAggregate
)
395 exceptionsToFlatten
.Add(currentInnerAsAggregate
);
399 flattenedExceptions
.Add(currentInnerException
);
405 return new AggregateException(Message
, flattenedExceptions
);
408 /// <summary>Gets a message that describes the exception.</summary>
409 public override string Message
413 if (m_innerExceptions
.Count
== 0)
418 StringBuilder sb
= StringBuilderCache
.Acquire();
419 sb
.Append(base.Message
);
421 for (int i
= 0; i
< m_innerExceptions
.Count
; i
++)
424 sb
.Append(m_innerExceptions
[i
].Message
);
428 return StringBuilderCache
.GetStringAndRelease(sb
);
433 /// Creates and returns a string representation of the current <see cref="AggregateException"/>.
435 /// <returns>A string representation of the current exception.</returns>
436 public override string ToString()
438 StringBuilder text
= new StringBuilder();
439 text
.Append(base.ToString());
441 for (int i
= 0; i
< m_innerExceptions
.Count
; i
++)
443 if (m_innerExceptions
[i
] == InnerException
)
444 continue; // Already logged in base.ToString()
446 text
.Append(Environment
.NewLineConst
+ InnerExceptionPrefix
);
447 text
.AppendFormat(CultureInfo
.InvariantCulture
, SR
.AggregateException_InnerException
, i
);
448 text
.Append(m_innerExceptions
[i
].ToString());
453 return text
.ToString();
457 /// This helper property is used by the DebuggerDisplay.
459 /// Note that we don't want to remove this property and change the debugger display to {InnerExceptions.Count}
460 /// because DebuggerDisplay should be a single property access or parameterless method call, so that the debugger
461 /// can use a fast path without using the expression evaluator.
463 /// See https://docs.microsoft.com/en-us/visualstudio/debugger/using-the-debuggerdisplay-attribute
465 private int InnerExceptionCount
=> InnerExceptions
.Count
;