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.
6 using System
.Collections
.Generic
;
7 using System
.Collections
.ObjectModel
;
8 using System
.Diagnostics
;
9 using System
.Globalization
;
10 using System
.Runtime
.ExceptionServices
;
11 using System
.Runtime
.Serialization
;
12 using System
.Security
;
14 using System
.Threading
;
18 /// <summary>Represents one or more errors that occur during application execution.</summary>
20 /// <see cref="AggregateException"/> is used to consolidate multiple failures into a single, throwable
24 [DebuggerDisplay("Count = {InnerExceptionCount}")]
25 [System
.Runtime
.CompilerServices
.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
26 public class AggregateException
: Exception
28 private readonly ReadOnlyCollection
<Exception
> m_innerExceptions
; // Complete set of exceptions. Do not rename (binary serialization)
31 /// Initializes a new instance of the <see cref="AggregateException"/> class.
33 public AggregateException()
34 : base(SR
.AggregateException_ctor_DefaultMessage
)
36 m_innerExceptions
= new ReadOnlyCollection
<Exception
>(Array
.Empty
<Exception
>());
40 /// Initializes a new instance of the <see cref="AggregateException"/> class with
41 /// a specified error message.
43 /// <param name="message">The error message that explains the reason for the exception.</param>
44 public AggregateException(string? message
)
47 m_innerExceptions
= new ReadOnlyCollection
<Exception
>(Array
.Empty
<Exception
>());
51 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
52 /// message and a reference to the inner exception that is the cause of this exception.
54 /// <param name="message">The error message that explains the reason for the exception.</param>
55 /// <param name="innerException">The exception that is the cause of the current exception.</param>
56 /// <exception cref="System.ArgumentNullException">The <paramref name="innerException"/> argument
57 /// is null.</exception>
58 public AggregateException(string? message
, Exception innerException
)
59 : base(message
, innerException
)
61 if (innerException
== null)
63 throw new ArgumentNullException(nameof(innerException
));
66 m_innerExceptions
= new ReadOnlyCollection
<Exception
>(new Exception
[] { innerException }
);
70 /// Initializes a new instance of the <see cref="AggregateException"/> class with
71 /// references to the inner exceptions that are the cause of this exception.
73 /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
74 /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
75 /// is null.</exception>
76 /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptions"/> is
78 public AggregateException(IEnumerable
<Exception
> innerExceptions
) :
79 this(SR
.AggregateException_ctor_DefaultMessage
, innerExceptions
)
84 /// Initializes a new instance of the <see cref="AggregateException"/> class with
85 /// references to the inner exceptions that are the cause of this exception.
87 /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
88 /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
89 /// is null.</exception>
90 /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptions"/> is
92 public AggregateException(params Exception
[] innerExceptions
) :
93 this(SR
.AggregateException_ctor_DefaultMessage
, innerExceptions
)
98 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
99 /// message and references to the inner exceptions that are the cause of this exception.
101 /// <param name="message">The error message that explains the reason for the exception.</param>
102 /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
103 /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
104 /// is null.</exception>
105 /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptions"/> is
106 /// null.</exception>
107 public AggregateException(string? message
, IEnumerable
<Exception
> innerExceptions
)
108 // 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
109 // null typed correctly. Otherwise, create an IList from the enumerable and pass that along.
110 : this(message
, innerExceptions
as IList
<Exception
> ?? (innerExceptions
== null ? (List
<Exception
>)null! : new List
<Exception
>(innerExceptions
)))
115 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
116 /// message and references to the inner exceptions that are the cause of this exception.
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 public AggregateException(string? message
, params Exception
[] innerExceptions
) :
125 this(message
, (IList
<Exception
>)innerExceptions
)
130 /// Allocates a new aggregate exception with the specified message and list of inner exceptions.
132 /// <param name="message">The error message that explains the reason for the exception.</param>
133 /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param>
134 /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptions"/> argument
135 /// is null.</exception>
136 /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptions"/> is
137 /// null.</exception>
138 private AggregateException(string? message
, IList
<Exception
> innerExceptions
)
139 : base(message
, innerExceptions
!= null && innerExceptions
.Count
> 0 ? innerExceptions
[0] : null)
141 if (innerExceptions
== null)
143 throw new ArgumentNullException(nameof(innerExceptions
));
146 // Copy exceptions to our internal array and validate them. We must copy them,
147 // because we're going to put them into a ReadOnlyCollection which simply reuses
148 // the list passed in to it. We don't want callers subsequently mutating.
149 Exception
[] exceptionsCopy
= new Exception
[innerExceptions
.Count
];
151 for (int i
= 0; i
< exceptionsCopy
.Length
; i
++)
153 exceptionsCopy
[i
] = innerExceptions
[i
];
155 if (exceptionsCopy
[i
] == null)
157 throw new ArgumentException(SR
.AggregateException_ctor_InnerExceptionNull
);
161 m_innerExceptions
= new ReadOnlyCollection
<Exception
>(exceptionsCopy
);
165 /// Initializes a new instance of the <see cref="AggregateException"/> class with
166 /// references to the inner exception dispatch info objects that represent the cause of this exception.
168 /// <param name="innerExceptionInfos">
169 /// Information about the exceptions that are the cause of the current exception.
171 /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument
172 /// is null.</exception>
173 /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is
174 /// null.</exception>
175 internal AggregateException(IEnumerable
<ExceptionDispatchInfo
> innerExceptionInfos
) :
176 this(SR
.AggregateException_ctor_DefaultMessage
, innerExceptionInfos
)
181 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error
182 /// message and references to the inner exception dispatch info objects that represent the cause of
185 /// <param name="message">The error message that explains the reason for the exception.</param>
186 /// <param name="innerExceptionInfos">
187 /// Information about the exceptions that are the cause of the current exception.
189 /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument
190 /// is null.</exception>
191 /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is
192 /// null.</exception>
193 internal AggregateException(string message
, IEnumerable
<ExceptionDispatchInfo
> innerExceptionInfos
)
194 // 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
195 // null typed correctly. Otherwise, create an IList from the enumerable and pass that along.
196 : this(message
, innerExceptionInfos
as IList
<ExceptionDispatchInfo
> ??
197 (innerExceptionInfos
== null ?
198 (List
<ExceptionDispatchInfo
>)null! :
199 new List
<ExceptionDispatchInfo
>(innerExceptionInfos
)))
204 /// Allocates a new aggregate exception with the specified message and list of inner
205 /// exception dispatch info objects.
207 /// <param name="message">The error message that explains the reason for the exception.</param>
208 /// <param name="innerExceptionInfos">
209 /// Information about the exceptions that are the cause of the current exception.
211 /// <exception cref="System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument
212 /// is null.</exception>
213 /// <exception cref="System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is
214 /// null.</exception>
215 private AggregateException(string message
, IList
<ExceptionDispatchInfo
> innerExceptionInfos
)
216 : base(message
, innerExceptionInfos
!= null && innerExceptionInfos
.Count
> 0 && innerExceptionInfos
[0] != null ?
217 innerExceptionInfos
[0].SourceException
: null)
219 if (innerExceptionInfos
== null)
221 throw new ArgumentNullException(nameof(innerExceptionInfos
));
224 // Copy exceptions to our internal array and validate them. We must copy them,
225 // because we're going to put them into a ReadOnlyCollection which simply reuses
226 // the list passed in to it. We don't want callers subsequently mutating.
227 Exception
[] exceptionsCopy
= new Exception
[innerExceptionInfos
.Count
];
229 for (int i
= 0; i
< exceptionsCopy
.Length
; i
++)
231 ExceptionDispatchInfo edi
= innerExceptionInfos
[i
];
232 if (edi
!= null) exceptionsCopy
[i
] = edi
.SourceException
;
234 if (exceptionsCopy
[i
] == null)
236 throw new ArgumentException(SR
.AggregateException_ctor_InnerExceptionNull
);
240 m_innerExceptions
= new ReadOnlyCollection
<Exception
>(exceptionsCopy
);
244 /// Initializes a new instance of the <see cref="AggregateException"/> class with serialized data.
246 /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/> that holds
247 /// the serialized object data about the exception being thrown.</param>
248 /// <param name="context">The <see cref="System.Runtime.Serialization.StreamingContext"/> that
249 /// contains contextual information about the source or destination. </param>
250 /// <exception cref="System.ArgumentNullException">The <paramref name="info"/> argument is null.</exception>
251 /// <exception cref="System.Runtime.Serialization.SerializationException">The exception could not be deserialized correctly.</exception>
252 protected AggregateException(SerializationInfo info
, StreamingContext context
) :
257 throw new ArgumentNullException(nameof(info
));
260 Exception
[]? innerExceptions
= info
.GetValue("InnerExceptions", typeof(Exception
[])) as Exception
[];
261 if (innerExceptions
is null)
263 throw new SerializationException(SR
.AggregateException_DeserializationFailure
);
266 m_innerExceptions
= new ReadOnlyCollection
<Exception
>(innerExceptions
);
270 /// Sets the <see cref="System.Runtime.Serialization.SerializationInfo"/> with information about
273 /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/> that holds
274 /// the serialized object data about the exception being thrown.</param>
275 /// <param name="context">The <see cref="System.Runtime.Serialization.StreamingContext"/> that
276 /// contains contextual information about the source or destination. </param>
277 /// <exception cref="System.ArgumentNullException">The <paramref name="info"/> argument is null.</exception>
278 public override void GetObjectData(SerializationInfo info
, StreamingContext context
)
280 base.GetObjectData(info
, context
);
282 Exception
[] innerExceptions
= new Exception
[m_innerExceptions
.Count
];
283 m_innerExceptions
.CopyTo(innerExceptions
, 0);
284 info
.AddValue("InnerExceptions", innerExceptions
, typeof(Exception
[]));
288 /// Returns the <see cref="System.AggregateException"/> that is the root cause of this exception.
290 public override Exception
GetBaseException()
292 // Returns the first inner AggregateException that contains more or less than one inner exception
294 // Recursively traverse the inner exceptions as long as the inner exception of type AggregateException and has only one inner exception
295 Exception
? back
= this;
296 AggregateException
? backAsAggregate
= this;
297 while (backAsAggregate
!= null && backAsAggregate
.InnerExceptions
.Count
== 1)
299 back
= back
!.InnerException
;
300 backAsAggregate
= back
as AggregateException
;
306 /// Gets a read-only collection of the <see cref="System.Exception"/> instances that caused the
307 /// current exception.
309 public ReadOnlyCollection
<Exception
> InnerExceptions
=> m_innerExceptions
;
313 /// Invokes a handler on each <see cref="System.Exception"/> contained by this <see
314 /// cref="AggregateException"/>.
316 /// <param name="predicate">The predicate to execute for each exception. The predicate accepts as an
317 /// argument the <see cref="System.Exception"/> to be processed and returns a Boolean to indicate
318 /// whether the exception was handled.</param>
320 /// Each invocation of the <paramref name="predicate"/> returns true or false to indicate whether the
321 /// <see cref="System.Exception"/> was handled. After all invocations, if any exceptions went
322 /// unhandled, all unhandled exceptions will be put into a new <see cref="AggregateException"/>
323 /// which will be thrown. Otherwise, the <see cref="Handle"/> method simply returns. If any
324 /// invocations of the <paramref name="predicate"/> throws an exception, it will halt the processing
325 /// of any more exceptions and immediately propagate the thrown exception as-is.
327 /// <exception cref="AggregateException">An exception contained by this <see
328 /// cref="AggregateException"/> was not handled.</exception>
329 /// <exception cref="System.ArgumentNullException">The <paramref name="predicate"/> argument is
330 /// null.</exception>
331 public void Handle(Func
<Exception
, bool> predicate
)
333 if (predicate
== null)
335 throw new ArgumentNullException(nameof(predicate
));
338 List
<Exception
>? unhandledExceptions
= null;
339 for (int i
= 0; i
< m_innerExceptions
.Count
; i
++)
341 // If the exception was not handled, lazily allocate a list of unhandled
342 // exceptions (to be rethrown later) and add it.
343 if (!predicate(m_innerExceptions
[i
]))
345 if (unhandledExceptions
== null)
347 unhandledExceptions
= new List
<Exception
>();
350 unhandledExceptions
.Add(m_innerExceptions
[i
]);
354 // If there are unhandled exceptions remaining, throw them.
355 if (unhandledExceptions
!= null)
357 throw new AggregateException(Message
, unhandledExceptions
);
363 /// Flattens the inner instances of <see cref="AggregateException"/> by expanding its contained <see cref="Exception"/> instances
364 /// into a new <see cref="AggregateException"/>
366 /// <returns>A new, flattened <see cref="AggregateException"/>.</returns>
368 /// If any inner exceptions are themselves instances of
369 /// <see cref="AggregateException"/>, this method will recursively flatten all of them. The
370 /// inner exceptions returned in the new <see cref="AggregateException"/>
371 /// will be the union of all of the inner exceptions from exception tree rooted at the provided
372 /// <see cref="AggregateException"/> instance.
374 public AggregateException
Flatten()
376 // Initialize a collection to contain the flattened exceptions.
377 List
<Exception
> flattenedExceptions
= new List
<Exception
>();
379 // Create a list to remember all aggregates to be flattened, this will be accessed like a FIFO queue
380 var exceptionsToFlatten
= new List
<AggregateException
> { this }
;
381 int nDequeueIndex
= 0;
383 // Continue removing and recursively flattening exceptions, until there are no more.
384 while (exceptionsToFlatten
.Count
> nDequeueIndex
)
386 // dequeue one from exceptionsToFlatten
387 IList
<Exception
> currentInnerExceptions
= exceptionsToFlatten
[nDequeueIndex
++].InnerExceptions
;
389 for (int i
= 0; i
< currentInnerExceptions
.Count
; i
++)
391 Exception currentInnerException
= currentInnerExceptions
[i
];
393 if (currentInnerException
== null)
398 // If this exception is an aggregate, keep it around for later. Otherwise,
399 // simply add it to the list of flattened exceptions to be returned.
400 if (currentInnerException
is AggregateException currentInnerAsAggregate
)
402 exceptionsToFlatten
.Add(currentInnerAsAggregate
);
406 flattenedExceptions
.Add(currentInnerException
);
412 return new AggregateException(Message
, flattenedExceptions
);
415 /// <summary>Gets a message that describes the exception.</summary>
416 public override string Message
420 if (m_innerExceptions
.Count
== 0)
425 StringBuilder sb
= StringBuilderCache
.Acquire();
426 sb
.Append(base.Message
);
428 for (int i
= 0; i
< m_innerExceptions
.Count
; i
++)
431 sb
.Append(m_innerExceptions
[i
].Message
);
435 return StringBuilderCache
.GetStringAndRelease(sb
);
440 /// Creates and returns a string representation of the current <see cref="AggregateException"/>.
442 /// <returns>A string representation of the current exception.</returns>
443 public override string ToString()
445 StringBuilder text
= new StringBuilder();
446 text
.Append(base.ToString());
448 for (int i
= 0; i
< m_innerExceptions
.Count
; i
++)
450 if (m_innerExceptions
[i
] == InnerException
)
451 continue; // Already logged in base.ToString()
453 text
.Append(Environment
.NewLine
).Append(InnerExceptionPrefix
);
454 text
.AppendFormat(CultureInfo
.InvariantCulture
, SR
.AggregateException_InnerException
, i
);
455 text
.Append(m_innerExceptions
[i
].ToString());
460 return text
.ToString();
464 /// This helper property is used by the DebuggerDisplay.
466 /// Note that we don't want to remove this property and change the debugger display to {InnerExceptions.Count}
467 /// because DebuggerDisplay should be a single property access or parameterless method call, so that the debugger
468 /// can use a fast path without using the expression evaluator.
470 /// See https://docs.microsoft.com/en-us/visualstudio/debugger/using-the-debuggerdisplay-attribute
472 private int InnerExceptionCount
=> InnerExceptions
.Count
;