Fix IDE0025 (use expression body for properties)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / AggregateException.cs
blob872f19e0cc84fc15958e64893897a8b8be13dcc4
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;
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;
13 using System.Text;
14 using System.Threading;
16 namespace System
18 /// <summary>Represents one or more errors that occur during application execution.</summary>
19 /// <remarks>
20 /// <see cref="AggregateException"/> is used to consolidate multiple failures into a single, throwable
21 /// exception object.
22 /// </remarks>
23 [Serializable]
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)
30 /// <summary>
31 /// Initializes a new instance of the <see cref="AggregateException"/> class.
32 /// </summary>
33 public AggregateException()
34 : base(SR.AggregateException_ctor_DefaultMessage)
36 m_innerExceptions = new ReadOnlyCollection<Exception>(Array.Empty<Exception>());
39 /// <summary>
40 /// Initializes a new instance of the <see cref="AggregateException"/> class with
41 /// a specified error message.
42 /// </summary>
43 /// <param name="message">The error message that explains the reason for the exception.</param>
44 public AggregateException(string? message)
45 : base(message)
47 m_innerExceptions = new ReadOnlyCollection<Exception>(Array.Empty<Exception>());
50 /// <summary>
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.
53 /// </summary>
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 });
69 /// <summary>
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.
72 /// </summary>
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
77 /// null.</exception>
78 public AggregateException(IEnumerable<Exception> innerExceptions) :
79 this(SR.AggregateException_ctor_DefaultMessage, innerExceptions)
83 /// <summary>
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.
86 /// </summary>
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
91 /// null.</exception>
92 public AggregateException(params Exception[] innerExceptions) :
93 this(SR.AggregateException_ctor_DefaultMessage, innerExceptions)
97 /// <summary>
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.
100 /// </summary>
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)))
114 /// <summary>
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.
117 /// </summary>
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)
129 /// <summary>
130 /// Allocates a new aggregate exception with the specified message and list of inner exceptions.
131 /// </summary>
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);
164 /// <summary>
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.
167 /// </summary>
168 /// <param name="innerExceptionInfos">
169 /// Information about the exceptions that are the cause of the current exception.
170 /// </param>
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)
180 /// <summary>
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
183 /// this exception.
184 /// </summary>
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.
188 /// </param>
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)))
203 /// <summary>
204 /// Allocates a new aggregate exception with the specified message and list of inner
205 /// exception dispatch info objects.
206 /// </summary>
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.
210 /// </param>
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);
243 /// <summary>
244 /// Initializes a new instance of the <see cref="AggregateException"/> class with serialized data.
245 /// </summary>
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) :
253 base(info, context)
255 if (info == null)
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);
269 /// <summary>
270 /// Sets the <see cref="System.Runtime.Serialization.SerializationInfo"/> with information about
271 /// the exception.
272 /// </summary>
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[]));
287 /// <summary>
288 /// Returns the <see cref="System.AggregateException"/> that is the root cause of this exception.
289 /// </summary>
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;
302 return back!;
305 /// <summary>
306 /// Gets a read-only collection of the <see cref="System.Exception"/> instances that caused the
307 /// current exception.
308 /// </summary>
309 public ReadOnlyCollection<Exception> InnerExceptions => m_innerExceptions;
312 /// <summary>
313 /// Invokes a handler on each <see cref="System.Exception"/> contained by this <see
314 /// cref="AggregateException"/>.
315 /// </summary>
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>
319 /// <remarks>
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.
326 /// </remarks>
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);
362 /// <summary>
363 /// Flattens the inner instances of <see cref="AggregateException"/> by expanding its contained <see cref="Exception"/> instances
364 /// into a new <see cref="AggregateException"/>
365 /// </summary>
366 /// <returns>A new, flattened <see cref="AggregateException"/>.</returns>
367 /// <remarks>
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.
373 /// </remarks>
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)
395 continue;
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);
404 else
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)
422 return base.Message;
425 StringBuilder sb = StringBuilderCache.Acquire();
426 sb.Append(base.Message);
427 sb.Append(' ');
428 for (int i = 0; i < m_innerExceptions.Count; i++)
430 sb.Append('(');
431 sb.Append(m_innerExceptions[i].Message);
432 sb.Append(") ");
434 sb.Length -= 1;
435 return StringBuilderCache.GetStringAndRelease(sb);
439 /// <summary>
440 /// Creates and returns a string representation of the current <see cref="AggregateException"/>.
441 /// </summary>
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());
456 text.Append("<---");
457 text.AppendLine();
460 return text.ToString();
463 /// <summary>
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
471 /// </summary>
472 private int InnerExceptionCount => InnerExceptions.Count;