Improve Dictionary TryGetValue size/perfomance (dotnet/coreclr#27195)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / AggregateException.cs
blob46baebfddd6e7bbe80f4372a0f867a5a2e332e89
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;
11 using System.Text;
13 namespace System
15 /// <summary>Represents one or more errors that occur during application execution.</summary>
16 /// <remarks>
17 /// <see cref="AggregateException"/> is used to consolidate multiple failures into a single, throwable
18 /// exception object.
19 /// </remarks>
20 [Serializable]
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)
27 /// <summary>
28 /// Initializes a new instance of the <see cref="AggregateException"/> class.
29 /// </summary>
30 public AggregateException()
31 : base(SR.AggregateException_ctor_DefaultMessage)
33 m_innerExceptions = new ReadOnlyCollection<Exception>(Array.Empty<Exception>());
36 /// <summary>
37 /// Initializes a new instance of the <see cref="AggregateException"/> class with
38 /// a specified error message.
39 /// </summary>
40 /// <param name="message">The error message that explains the reason for the exception.</param>
41 public AggregateException(string? message)
42 : base(message)
44 m_innerExceptions = new ReadOnlyCollection<Exception>(Array.Empty<Exception>());
47 /// <summary>
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.
50 /// </summary>
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 });
66 /// <summary>
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.
69 /// </summary>
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
74 /// null.</exception>
75 public AggregateException(IEnumerable<Exception> innerExceptions) :
76 this(SR.AggregateException_ctor_DefaultMessage, innerExceptions)
80 /// <summary>
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.
83 /// </summary>
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
88 /// null.</exception>
89 public AggregateException(params Exception[] innerExceptions) :
90 this(SR.AggregateException_ctor_DefaultMessage, innerExceptions)
94 /// <summary>
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.
97 /// </summary>
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)))
111 /// <summary>
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.
114 /// </summary>
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)
126 /// <summary>
127 /// Allocates a new aggregate exception with the specified message and list of inner exceptions.
128 /// </summary>
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);
161 /// <summary>
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.
164 /// </summary>
165 /// <param name="innerExceptionInfos">
166 /// Information about the exceptions that are the cause of the current exception.
167 /// </param>
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)
177 /// <summary>
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
180 /// this exception.
181 /// </summary>
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.
185 /// </param>
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)))
200 /// <summary>
201 /// Allocates a new aggregate exception with the specified message and list of inner
202 /// exception dispatch info objects.
203 /// </summary>
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.
207 /// </param>
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);
240 /// <summary>
241 /// Initializes a new instance of the <see cref="AggregateException"/> class with serialized data.
242 /// </summary>
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) :
250 base(info, context)
252 if (info == null)
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);
266 /// <summary>
267 /// Sets the <see cref="System.Runtime.Serialization.SerializationInfo"/> with information about
268 /// the exception.
269 /// </summary>
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[]));
284 /// <summary>
285 /// Returns the <see cref="System.AggregateException"/> that is the root cause of this exception.
286 /// </summary>
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;
299 return back!;
302 /// <summary>
303 /// Gets a read-only collection of the <see cref="System.Exception"/> instances that caused the
304 /// current exception.
305 /// </summary>
306 public ReadOnlyCollection<Exception> InnerExceptions => m_innerExceptions;
309 /// <summary>
310 /// Invokes a handler on each <see cref="System.Exception"/> contained by this <see
311 /// cref="AggregateException"/>.
312 /// </summary>
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>
316 /// <remarks>
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.
323 /// </remarks>
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);
355 /// <summary>
356 /// Flattens the inner instances of <see cref="AggregateException"/> by expanding its contained <see cref="Exception"/> instances
357 /// into a new <see cref="AggregateException"/>
358 /// </summary>
359 /// <returns>A new, flattened <see cref="AggregateException"/>.</returns>
360 /// <remarks>
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.
366 /// </remarks>
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)
388 continue;
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);
397 else
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)
415 return base.Message;
418 StringBuilder sb = StringBuilderCache.Acquire();
419 sb.Append(base.Message);
420 sb.Append(' ');
421 for (int i = 0; i < m_innerExceptions.Count; i++)
423 sb.Append('(');
424 sb.Append(m_innerExceptions[i].Message);
425 sb.Append(") ");
427 sb.Length--;
428 return StringBuilderCache.GetStringAndRelease(sb);
432 /// <summary>
433 /// Creates and returns a string representation of the current <see cref="AggregateException"/>.
434 /// </summary>
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());
449 text.Append("<---");
450 text.AppendLine();
453 return text.ToString();
456 /// <summary>
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
464 /// </summary>
465 private int InnerExceptionCount => InnerExceptions.Count;