1 // ==++== 2 // 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // 5 // ==--== 6 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ 7 // 8 // AggregateException.cs 9 // 10 // <OWNER>Microsoft</OWNER> 11 // 12 // Public type to communicate multiple failures to an end-user. 13 // 14 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 15 16 using System; 17 using System.Collections.Generic; 18 using System.Collections.ObjectModel; 19 using System.Diagnostics; 20 using System.Globalization; 21 using System.Runtime.ExceptionServices; 22 using System.Runtime.Serialization; 23 using System.Security; 24 using System.Threading; 25 26 namespace System 27 { 28 29 /// <summary>Represents one or more errors that occur during application execution.</summary> 30 /// <remarks> 31 /// <see cref="AggregateException"/> is used to consolidate multiple failures into a single, throwable 32 /// exception object. 33 /// </remarks> 34 [Serializable] 35 [DebuggerDisplay("Count = {InnerExceptionCount}")] 36 public class AggregateException : Exception 37 { 38 39 private ReadOnlyCollection<Exception> m_innerExceptions; // Complete set of exceptions. 40 41 /// <summary> 42 /// Initializes a new instance of the <see cref="AggregateException"/> class. 43 /// </summary> AggregateException()44 public AggregateException() 45 : base(Environment.GetResourceString("AggregateException_ctor_DefaultMessage")) 46 { 47 m_innerExceptions = new ReadOnlyCollection<Exception>(new Exception[0]); 48 } 49 50 /// <summary> 51 /// Initializes a new instance of the <see cref="AggregateException"/> class with 52 /// a specified error message. 53 /// </summary> 54 /// <param name="message">The error message that explains the reason for the exception.</param> AggregateException(string message)55 public AggregateException(string message) 56 : base(message) 57 { 58 m_innerExceptions = new ReadOnlyCollection<Exception>(new Exception[0]); 59 } 60 61 /// <summary> 62 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error 63 /// message and a reference to the inner exception that is the cause of this exception. 64 /// </summary> 65 /// <param name="message">The error message that explains the reason for the exception.</param> 66 /// <param name="innerException">The exception that is the cause of the current exception.</param> 67 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerException"/> argument 68 /// is null.</exception> AggregateException(string message, Exception innerException)69 public AggregateException(string message, Exception innerException) 70 : base(message, innerException) 71 { 72 if (innerException == null) 73 { 74 throw new ArgumentNullException("innerException"); 75 } 76 77 m_innerExceptions = new ReadOnlyCollection<Exception>(new Exception[] { innerException }); 78 } 79 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="T:System.ArgumentNullException">The <paramref name="innerExceptions"/> argument 86 /// is null.</exception> 87 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptions"/> is 88 /// null.</exception> AggregateException(IEnumerable<Exception> innerExceptions)89 public AggregateException(IEnumerable<Exception> innerExceptions) : 90 this(Environment.GetResourceString("AggregateException_ctor_DefaultMessage"), innerExceptions) 91 { 92 } 93 94 /// <summary> 95 /// Initializes a new instance of the <see cref="AggregateException"/> class with 96 /// references to the inner exceptions that are the cause of this exception. 97 /// </summary> 98 /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param> 99 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptions"/> argument 100 /// is null.</exception> 101 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptions"/> is 102 /// null.</exception> AggregateException(params Exception[] innerExceptions)103 public AggregateException(params Exception[] innerExceptions) : 104 this(Environment.GetResourceString("AggregateException_ctor_DefaultMessage"), innerExceptions) 105 { 106 } 107 108 /// <summary> 109 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error 110 /// message and references to the inner exceptions that are the cause of this exception. 111 /// </summary> 112 /// <param name="message">The error message that explains the reason for the exception.</param> 113 /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param> 114 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptions"/> argument 115 /// is null.</exception> 116 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptions"/> is 117 /// null.</exception> AggregateException(string message, IEnumerable<Exception> innerExceptions)118 public AggregateException(string message, IEnumerable<Exception> innerExceptions) 119 // 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 120 // null typed correctly. Otherwise, create an IList from the enumerable and pass that along. 121 : this(message, innerExceptions as IList<Exception> ?? (innerExceptions == null ? (List<Exception>)null : new List<Exception>(innerExceptions))) 122 { 123 } 124 125 /// <summary> 126 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error 127 /// message and references to the inner exceptions that are the cause of this exception. 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="T:System.ArgumentNullException">The <paramref name="innerExceptions"/> argument 132 /// is null.</exception> 133 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptions"/> is 134 /// null.</exception> AggregateException(string message, params Exception[] innerExceptions)135 public AggregateException(string message, params Exception[] innerExceptions) : 136 this(message, (IList<Exception>)innerExceptions) 137 { 138 } 139 140 /// <summary> 141 /// Allocates a new aggregate exception with the specified message and list of inner exceptions. 142 /// </summary> 143 /// <param name="message">The error message that explains the reason for the exception.</param> 144 /// <param name="innerExceptions">The exceptions that are the cause of the current exception.</param> 145 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptions"/> argument 146 /// is null.</exception> 147 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptions"/> is 148 /// null.</exception> AggregateException(string message, IList<Exception> innerExceptions)149 private AggregateException(string message, IList<Exception> innerExceptions) 150 : base(message, innerExceptions != null && innerExceptions.Count > 0 ? innerExceptions[0] : null) 151 { 152 if (innerExceptions == null) 153 { 154 throw new ArgumentNullException("innerExceptions"); 155 } 156 157 // Copy exceptions to our internal array and validate them. We must copy them, 158 // because we're going to put them into a ReadOnlyCollection which simply reuses 159 // the list passed in to it. We don't want callers subsequently mutating. 160 Exception[] exceptionsCopy = new Exception[innerExceptions.Count]; 161 162 for (int i = 0; i < exceptionsCopy.Length; i++) 163 { 164 exceptionsCopy[i] = innerExceptions[i]; 165 166 if (exceptionsCopy[i] == null) 167 { 168 throw new ArgumentException(Environment.GetResourceString("AggregateException_ctor_InnerExceptionNull")); 169 } 170 } 171 172 m_innerExceptions = new ReadOnlyCollection<Exception>(exceptionsCopy); 173 } 174 175 /// <summary> 176 /// Initializes a new instance of the <see cref="AggregateException"/> class with 177 /// references to the inner exception dispatch info objects that represent the cause of this exception. 178 /// </summary> 179 /// <param name="innerExceptionInfos"> 180 /// Information about the exceptions that are the cause of the current exception. 181 /// </param> 182 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument 183 /// is null.</exception> 184 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is 185 /// null.</exception> AggregateException(IEnumerable<ExceptionDispatchInfo> innerExceptionInfos)186 internal AggregateException(IEnumerable<ExceptionDispatchInfo> innerExceptionInfos) : 187 this(Environment.GetResourceString("AggregateException_ctor_DefaultMessage"), innerExceptionInfos) 188 { 189 } 190 191 /// <summary> 192 /// Initializes a new instance of the <see cref="AggregateException"/> class with a specified error 193 /// message and references to the inner exception dispatch info objects that represent the cause of 194 /// this exception. 195 /// </summary> 196 /// <param name="message">The error message that explains the reason for the exception.</param> 197 /// <param name="innerExceptionInfos"> 198 /// Information about the exceptions that are the cause of the current exception. 199 /// </param> 200 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument 201 /// is null.</exception> 202 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is 203 /// null.</exception> AggregateException(string message, IEnumerable<ExceptionDispatchInfo> innerExceptionInfos)204 internal AggregateException(string message, IEnumerable<ExceptionDispatchInfo> innerExceptionInfos) 205 // 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 206 // null typed correctly. Otherwise, create an IList from the enumerable and pass that along. 207 : this(message, innerExceptionInfos as IList<ExceptionDispatchInfo> ?? 208 (innerExceptionInfos == null ? 209 (List<ExceptionDispatchInfo>)null : 210 new List<ExceptionDispatchInfo>(innerExceptionInfos))) 211 { 212 } 213 214 /// <summary> 215 /// Allocates a new aggregate exception with the specified message and list of inner 216 /// exception dispatch info objects. 217 /// </summary> 218 /// <param name="message">The error message that explains the reason for the exception.</param> 219 /// <param name="innerExceptionInfos"> 220 /// Information about the exceptions that are the cause of the current exception. 221 /// </param> 222 /// <exception cref="T:System.ArgumentNullException">The <paramref name="innerExceptionInfos"/> argument 223 /// is null.</exception> 224 /// <exception cref="T:System.ArgumentException">An element of <paramref name="innerExceptionInfos"/> is 225 /// null.</exception> AggregateException(string message, IList<ExceptionDispatchInfo> innerExceptionInfos)226 private AggregateException(string message, IList<ExceptionDispatchInfo> innerExceptionInfos) 227 : base(message, innerExceptionInfos != null && innerExceptionInfos.Count > 0 && innerExceptionInfos[0] != null ? 228 innerExceptionInfos[0].SourceException : null) 229 { 230 if (innerExceptionInfos == null) 231 { 232 throw new ArgumentNullException("innerExceptionInfos"); 233 } 234 235 // Copy exceptions to our internal array and validate them. We must copy them, 236 // because we're going to put them into a ReadOnlyCollection which simply reuses 237 // the list passed in to it. We don't want callers subsequently mutating. 238 Exception[] exceptionsCopy = new Exception[innerExceptionInfos.Count]; 239 240 for (int i = 0; i < exceptionsCopy.Length; i++) 241 { 242 var edi = innerExceptionInfos[i]; 243 if (edi != null) exceptionsCopy[i] = edi.SourceException; 244 245 if (exceptionsCopy[i] == null) 246 { 247 throw new ArgumentException(Environment.GetResourceString("AggregateException_ctor_InnerExceptionNull")); 248 } 249 } 250 251 m_innerExceptions = new ReadOnlyCollection<Exception>(exceptionsCopy); 252 } 253 254 /// <summary> 255 /// Initializes a new instance of the <see cref="AggregateException"/> class with serialized data. 256 /// </summary> 257 /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds 258 /// the serialized object data about the exception being thrown.</param> 259 /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that 260 /// contains contextual information about the source or destination. </param> 261 /// <exception cref="T:System.ArgumentNullException">The <paramref name="info"/> argument is null.</exception> 262 /// <exception cref="T:System.Runtime.Serialization.SerializationException">The exception could not be deserialized correctly.</exception> 263 [SecurityCritical] AggregateException(SerializationInfo info, StreamingContext context)264 protected AggregateException(SerializationInfo info, StreamingContext context) : 265 base(info, context) 266 { 267 if (info == null) 268 { 269 throw new ArgumentNullException("info"); 270 } 271 272 Exception[] innerExceptions = info.GetValue("InnerExceptions", typeof(Exception[])) as Exception[]; 273 if (innerExceptions == null) 274 { 275 throw new SerializationException(Environment.GetResourceString("AggregateException_DeserializationFailure")); 276 } 277 278 m_innerExceptions = new ReadOnlyCollection<Exception>(innerExceptions); 279 } 280 281 /// <summary> 282 /// Sets the <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with information about 283 /// the exception. 284 /// </summary> 285 /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds 286 /// the serialized object data about the exception being thrown.</param> 287 /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that 288 /// contains contextual information about the source or destination. </param> 289 /// <exception cref="T:System.ArgumentNullException">The <paramref name="info"/> argument is null.</exception> 290 [SecurityCritical] GetObjectData(SerializationInfo info, StreamingContext context)291 public override void GetObjectData(SerializationInfo info, StreamingContext context) 292 { 293 if (info == null) 294 { 295 throw new ArgumentNullException("info"); 296 } 297 298 base.GetObjectData(info, context); 299 300 Exception[] innerExceptions = new Exception[m_innerExceptions.Count]; 301 m_innerExceptions.CopyTo(innerExceptions, 0); 302 info.AddValue("InnerExceptions", innerExceptions, typeof(Exception[])); 303 } 304 305 /// <summary> 306 /// Returns the <see cref="System.AggregateException"/> that is the root cause of this exception. 307 /// </summary> GetBaseException()308 public override Exception GetBaseException() 309 { 310 // Returns the first inner AggregateException that contains more or less than one inner exception 311 312 // Recursively traverse the inner exceptions as long as the inner exception of type AggregateException and has only one inner exception 313 Exception back = this; 314 AggregateException backAsAggregate = this; 315 while (backAsAggregate != null && backAsAggregate.InnerExceptions.Count == 1) 316 { 317 back = back.InnerException; 318 backAsAggregate = back as AggregateException; 319 } 320 return back; 321 } 322 323 /// <summary> 324 /// Gets a read-only collection of the <see cref="T:System.Exception"/> instances that caused the 325 /// current exception. 326 /// </summary> 327 public ReadOnlyCollection<Exception> InnerExceptions 328 { 329 get { return m_innerExceptions; } 330 } 331 332 333 /// <summary> 334 /// Invokes a handler on each <see cref="T:System.Exception"/> contained by this <see 335 /// cref="AggregateException"/>. 336 /// </summary> 337 /// <param name="predicate">The predicate to execute for each exception. The predicate accepts as an 338 /// argument the <see cref="T:System.Exception"/> to be processed and returns a Boolean to indicate 339 /// whether the exception was handled.</param> 340 /// <remarks> 341 /// Each invocation of the <paramref name="predicate"/> returns true or false to indicate whether the 342 /// <see cref="T:System.Exception"/> was handled. After all invocations, if any exceptions went 343 /// unhandled, all unhandled exceptions will be put into a new <see cref="AggregateException"/> 344 /// which will be thrown. Otherwise, the <see cref="Handle"/> method simply returns. If any 345 /// invocations of the <paramref name="predicate"/> throws an exception, it will halt the processing 346 /// of any more exceptions and immediately propagate the thrown exception as-is. 347 /// </remarks> 348 /// <exception cref="AggregateException">An exception contained by this <see 349 /// cref="AggregateException"/> was not handled.</exception> 350 /// <exception cref="T:System.ArgumentNullException">The <paramref name="predicate"/> argument is 351 /// null.</exception> Handle(Func<Exception, bool> predicate)352 public void Handle(Func<Exception, bool> predicate) 353 { 354 if (predicate == null) 355 { 356 throw new ArgumentNullException("predicate"); 357 } 358 359 List<Exception> unhandledExceptions = null; 360 for (int i = 0; i < m_innerExceptions.Count; i++) 361 { 362 // If the exception was not handled, lazily allocate a list of unhandled 363 // exceptions (to be rethrown later) and add it. 364 if (!predicate(m_innerExceptions[i])) 365 { 366 if (unhandledExceptions == null) 367 { 368 unhandledExceptions = new List<Exception>(); 369 } 370 371 unhandledExceptions.Add(m_innerExceptions[i]); 372 } 373 } 374 375 // If there are unhandled exceptions remaining, throw them. 376 if (unhandledExceptions != null) 377 { 378 throw new AggregateException(Message, unhandledExceptions); 379 } 380 } 381 382 383 /// <summary> 384 /// Flattens an <see cref="AggregateException"/> instances into a single, new instance. 385 /// </summary> 386 /// <returns>A new, flattened <see cref="AggregateException"/>.</returns> 387 /// <remarks> 388 /// If any inner exceptions are themselves instances of 389 /// <see cref="AggregateException"/>, this method will recursively flatten all of them. The 390 /// inner exceptions returned in the new <see cref="AggregateException"/> 391 /// will be the union of all of the the inner exceptions from exception tree rooted at the provided 392 /// <see cref="AggregateException"/> instance. 393 /// </remarks> Flatten()394 public AggregateException Flatten() 395 { 396 // Initialize a collection to contain the flattened exceptions. 397 List<Exception> flattenedExceptions = new List<Exception>(); 398 399 // Create a list to remember all aggregates to be flattened, this will be accessed like a FIFO queue 400 List<AggregateException> exceptionsToFlatten = new List<AggregateException>(); 401 exceptionsToFlatten.Add(this); 402 int nDequeueIndex = 0; 403 404 // Continue removing and recursively flattening exceptions, until there are no more. 405 while (exceptionsToFlatten.Count > nDequeueIndex) 406 { 407 // dequeue one from exceptionsToFlatten 408 IList<Exception> currentInnerExceptions = exceptionsToFlatten[nDequeueIndex++].InnerExceptions; 409 410 for (int i = 0; i < currentInnerExceptions.Count; i++) 411 { 412 Exception currentInnerException = currentInnerExceptions[i]; 413 414 if (currentInnerException == null) 415 { 416 continue; 417 } 418 419 AggregateException currentInnerAsAggregate = currentInnerException as AggregateException; 420 421 // If this exception is an aggregate, keep it around for later. Otherwise, 422 // simply add it to the list of flattened exceptions to be returned. 423 if (currentInnerAsAggregate != null) 424 { 425 exceptionsToFlatten.Add(currentInnerAsAggregate); 426 } 427 else 428 { 429 flattenedExceptions.Add(currentInnerException); 430 } 431 } 432 } 433 434 435 return new AggregateException(Message, flattenedExceptions); 436 } 437 438 /// <summary> 439 /// Creates and returns a string representation of the current <see cref="AggregateException"/>. 440 /// </summary> 441 /// <returns>A string representation of the current exception.</returns> ToString()442 public override string ToString() 443 { 444 string text = base.ToString(); 445 446 for (int i = 0; i < m_innerExceptions.Count; i++) 447 { 448 text = String.Format( 449 CultureInfo.InvariantCulture, 450 Environment.GetResourceString("AggregateException_ToString"), 451 text, Environment.NewLine, i, m_innerExceptions[i].ToString(), "<---", Environment.NewLine); 452 } 453 454 return text; 455 } 456 457 /// <summary> 458 /// This helper property is used by the DebuggerDisplay. 459 /// 460 /// Note that we don't want to remove this property and change the debugger display to {InnerExceptions.Count} 461 /// because DebuggerDisplay should be a single property access or parameterless method call, so that the debugger 462 /// can use a fast path without using the expression evaluator. 463 /// 464 /// See http://msdn.microsoft.com/en-us/library/x810d419.aspx 465 /// </summary> 466 private int InnerExceptionCount 467 { 468 get 469 { 470 return InnerExceptions.Count; 471 } 472 } 473 } 474 475 } 476