// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.Runtime.ExceptionServices; using System.Runtime.Serialization; using System.Security; using System.Text; using System.Threading; namespace System { /// Represents one or more errors that occur during application execution. /// /// is used to consolidate multiple failures into a single, throwable /// exception object. /// [Serializable] [DebuggerDisplay("Count = {InnerExceptionCount}")] [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] public class AggregateException : Exception { private ReadOnlyCollection m_innerExceptions; // Complete set of exceptions. Do not rename (binary serialization) /// /// Initializes a new instance of the class. /// public AggregateException() : base(SR.AggregateException_ctor_DefaultMessage) { m_innerExceptions = new ReadOnlyCollection(Array.Empty()); } /// /// Initializes a new instance of the class with /// a specified error message. /// /// The error message that explains the reason for the exception. public AggregateException(string? message) : base(message) { m_innerExceptions = new ReadOnlyCollection(Array.Empty()); } /// /// Initializes a new instance of the class with a specified error /// message and a reference to the inner exception that is the cause of this exception. /// /// The error message that explains the reason for the exception. /// The exception that is the cause of the current exception. /// The argument /// is null. public AggregateException(string? message, Exception innerException) : base(message, innerException) { if (innerException == null) { throw new ArgumentNullException(nameof(innerException)); } m_innerExceptions = new ReadOnlyCollection(new Exception[] { innerException }); } /// /// Initializes a new instance of the class with /// references to the inner exceptions that are the cause of this exception. /// /// The exceptions that are the cause of the current exception. /// The argument /// is null. /// An element of is /// null. public AggregateException(IEnumerable innerExceptions) : this(SR.AggregateException_ctor_DefaultMessage, innerExceptions) { } /// /// Initializes a new instance of the class with /// references to the inner exceptions that are the cause of this exception. /// /// The exceptions that are the cause of the current exception. /// The argument /// is null. /// An element of is /// null. public AggregateException(params Exception[] innerExceptions) : this(SR.AggregateException_ctor_DefaultMessage, innerExceptions) { } /// /// Initializes a new instance of the class with a specified error /// message and references to the inner exceptions that are the cause of this exception. /// /// The error message that explains the reason for the exception. /// The exceptions that are the cause of the current exception. /// The argument /// is null. /// An element of is /// null. public AggregateException(string? message, IEnumerable innerExceptions) // 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 // null typed correctly. Otherwise, create an IList from the enumerable and pass that along. : this(message, innerExceptions as IList ?? (innerExceptions == null ? (List)null! : new List(innerExceptions))) { } /// /// Initializes a new instance of the class with a specified error /// message and references to the inner exceptions that are the cause of this exception. /// /// The error message that explains the reason for the exception. /// The exceptions that are the cause of the current exception. /// The argument /// is null. /// An element of is /// null. public AggregateException(string? message, params Exception[] innerExceptions) : this(message, (IList)innerExceptions) { } /// /// Allocates a new aggregate exception with the specified message and list of inner exceptions. /// /// The error message that explains the reason for the exception. /// The exceptions that are the cause of the current exception. /// The argument /// is null. /// An element of is /// null. private AggregateException(string? message, IList innerExceptions) : base(message, innerExceptions != null && innerExceptions.Count > 0 ? innerExceptions[0] : null) { if (innerExceptions == null) { throw new ArgumentNullException(nameof(innerExceptions)); } // Copy exceptions to our internal array and validate them. We must copy them, // because we're going to put them into a ReadOnlyCollection which simply reuses // the list passed in to it. We don't want callers subsequently mutating. Exception[] exceptionsCopy = new Exception[innerExceptions.Count]; for (int i = 0; i < exceptionsCopy.Length; i++) { exceptionsCopy[i] = innerExceptions[i]; if (exceptionsCopy[i] == null) { throw new ArgumentException(SR.AggregateException_ctor_InnerExceptionNull); } } m_innerExceptions = new ReadOnlyCollection(exceptionsCopy); } /// /// Initializes a new instance of the class with /// references to the inner exception dispatch info objects that represent the cause of this exception. /// /// /// Information about the exceptions that are the cause of the current exception. /// /// The argument /// is null. /// An element of is /// null. internal AggregateException(IEnumerable innerExceptionInfos) : this(SR.AggregateException_ctor_DefaultMessage, innerExceptionInfos) { } /// /// Initializes a new instance of the class with a specified error /// message and references to the inner exception dispatch info objects that represent the cause of /// this exception. /// /// The error message that explains the reason for the exception. /// /// Information about the exceptions that are the cause of the current exception. /// /// The argument /// is null. /// An element of is /// null. internal AggregateException(string message, IEnumerable innerExceptionInfos) // 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 // null typed correctly. Otherwise, create an IList from the enumerable and pass that along. : this(message, innerExceptionInfos as IList ?? (innerExceptionInfos == null ? (List)null! : new List(innerExceptionInfos))) { } /// /// Allocates a new aggregate exception with the specified message and list of inner /// exception dispatch info objects. /// /// The error message that explains the reason for the exception. /// /// Information about the exceptions that are the cause of the current exception. /// /// The argument /// is null. /// An element of is /// null. private AggregateException(string message, IList innerExceptionInfos) : base(message, innerExceptionInfos != null && innerExceptionInfos.Count > 0 && innerExceptionInfos[0] != null ? innerExceptionInfos[0].SourceException : null) { if (innerExceptionInfos == null) { throw new ArgumentNullException(nameof(innerExceptionInfos)); } // Copy exceptions to our internal array and validate them. We must copy them, // because we're going to put them into a ReadOnlyCollection which simply reuses // the list passed in to it. We don't want callers subsequently mutating. Exception[] exceptionsCopy = new Exception[innerExceptionInfos.Count]; for (int i = 0; i < exceptionsCopy.Length; i++) { var edi = innerExceptionInfos[i]; if (edi != null) exceptionsCopy[i] = edi.SourceException; if (exceptionsCopy[i] == null) { throw new ArgumentException(SR.AggregateException_ctor_InnerExceptionNull); } } m_innerExceptions = new ReadOnlyCollection(exceptionsCopy); } /// /// Initializes a new instance of the class with serialized data. /// /// The that holds /// the serialized object data about the exception being thrown. /// The that /// contains contextual information about the source or destination. /// The argument is null. /// The exception could not be deserialized correctly. protected AggregateException(SerializationInfo info, StreamingContext context) : base(info, context) { if (info == null) { throw new ArgumentNullException(nameof(info)); } Exception[]? innerExceptions = info.GetValue("InnerExceptions", typeof(Exception[])) as Exception[]; if (innerExceptions == null) { throw new SerializationException(SR.AggregateException_DeserializationFailure); } m_innerExceptions = new ReadOnlyCollection(innerExceptions); } /// /// Sets the with information about /// the exception. /// /// The that holds /// the serialized object data about the exception being thrown. /// The that /// contains contextual information about the source or destination. /// The argument is null. public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); Exception[] innerExceptions = new Exception[m_innerExceptions.Count]; m_innerExceptions.CopyTo(innerExceptions, 0); info.AddValue("InnerExceptions", innerExceptions, typeof(Exception[])); } /// /// Returns the that is the root cause of this exception. /// public override Exception GetBaseException() { // Returns the first inner AggregateException that contains more or less than one inner exception // Recursively traverse the inner exceptions as long as the inner exception of type AggregateException and has only one inner exception Exception? back = this; AggregateException? backAsAggregate = this; while (backAsAggregate != null && backAsAggregate.InnerExceptions.Count == 1) { back = back!.InnerException; backAsAggregate = back as AggregateException; } return back!; } /// /// Gets a read-only collection of the instances that caused the /// current exception. /// public ReadOnlyCollection InnerExceptions { get { return m_innerExceptions; } } /// /// Invokes a handler on each contained by this . /// /// The predicate to execute for each exception. The predicate accepts as an /// argument the to be processed and returns a Boolean to indicate /// whether the exception was handled. /// /// Each invocation of the returns true or false to indicate whether the /// was handled. After all invocations, if any exceptions went /// unhandled, all unhandled exceptions will be put into a new /// which will be thrown. Otherwise, the method simply returns. If any /// invocations of the throws an exception, it will halt the processing /// of any more exceptions and immediately propagate the thrown exception as-is. /// /// An exception contained by this was not handled. /// The argument is /// null. public void Handle(Func predicate) { if (predicate == null) { throw new ArgumentNullException(nameof(predicate)); } List? unhandledExceptions = null; for (int i = 0; i < m_innerExceptions.Count; i++) { // If the exception was not handled, lazily allocate a list of unhandled // exceptions (to be rethrown later) and add it. if (!predicate(m_innerExceptions[i])) { if (unhandledExceptions == null) { unhandledExceptions = new List(); } unhandledExceptions.Add(m_innerExceptions[i]); } } // If there are unhandled exceptions remaining, throw them. if (unhandledExceptions != null) { throw new AggregateException(Message, unhandledExceptions); } } /// /// Flattens the inner instances of by expanding its contained instances /// into a new /// /// A new, flattened . /// /// If any inner exceptions are themselves instances of /// , this method will recursively flatten all of them. The /// inner exceptions returned in the new /// will be the union of all of the inner exceptions from exception tree rooted at the provided /// instance. /// public AggregateException Flatten() { // Initialize a collection to contain the flattened exceptions. List flattenedExceptions = new List(); // Create a list to remember all aggregates to be flattened, this will be accessed like a FIFO queue List exceptionsToFlatten = new List(); exceptionsToFlatten.Add(this); int nDequeueIndex = 0; // Continue removing and recursively flattening exceptions, until there are no more. while (exceptionsToFlatten.Count > nDequeueIndex) { // dequeue one from exceptionsToFlatten IList currentInnerExceptions = exceptionsToFlatten[nDequeueIndex++].InnerExceptions; for (int i = 0; i < currentInnerExceptions.Count; i++) { Exception currentInnerException = currentInnerExceptions[i]; if (currentInnerException == null) { continue; } // If this exception is an aggregate, keep it around for later. Otherwise, // simply add it to the list of flattened exceptions to be returned. if (currentInnerException is AggregateException currentInnerAsAggregate) { exceptionsToFlatten.Add(currentInnerAsAggregate); } else { flattenedExceptions.Add(currentInnerException); } } } return new AggregateException(Message, flattenedExceptions); } /// Gets a message that describes the exception. public override string Message { get { if (m_innerExceptions.Count == 0) { return base.Message; } StringBuilder sb = StringBuilderCache.Acquire(); sb.Append(base.Message); sb.Append(' '); for (int i = 0; i < m_innerExceptions.Count; i++) { sb.Append('('); sb.Append(m_innerExceptions[i].Message); sb.Append(") "); } sb.Length -= 1; return StringBuilderCache.GetStringAndRelease(sb); } } /// /// Creates and returns a string representation of the current . /// /// A string representation of the current exception. public override string ToString() { StringBuilder text = new StringBuilder(); text.Append(base.ToString()); for (int i = 0; i < m_innerExceptions.Count; i++) { if (m_innerExceptions[i] == InnerException) continue; // Already logged in base.ToString() text.Append(Environment.NewLine).Append(InnerExceptionPrefix); text.AppendFormat(CultureInfo.InvariantCulture, SR.AggregateException_InnerException, i); text.Append(m_innerExceptions[i].ToString()); text.Append("<---"); text.AppendLine(); } return text.ToString(); } /// /// This helper property is used by the DebuggerDisplay. /// /// Note that we don't want to remove this property and change the debugger display to {InnerExceptions.Count} /// because DebuggerDisplay should be a single property access or parameterless method call, so that the debugger /// can use a fast path without using the expression evaluator. /// /// See https://docs.microsoft.com/en-us/visualstudio/debugger/using-the-debuggerdisplay-attribute /// private int InnerExceptionCount { get { return InnerExceptions.Count; } } } }