In an application I work on, any business logic error causes an exception to be thrown, and the calling code handles the exception. This pattern is used throughout the application and works well.
I have a situation where I will be attempting to execute a number of business tasks from inside the business layer. The requirement for this is that a failure of one task should not cause the process to terminate. Other tasks should still be able to execute. In other words, this is not an atomic operation. The problem I have is that at the end of the operation, I wish to notify the calling code that an exception or exceptions did occur by throwing an exception. Consider the following psuedo-code snippet:
function DoTasks(MyTask[] taskList) { foreach(MyTask task in taskList) { try { DoTask(task); } catch(Exception ex) { log.add(ex); } } //I want to throw something here if any exception occurred }
What do I throw? I have encountered this pattern before in my career. In the past I have kept a list of all exceptions, then thrown an exception that contains all the caught exceptions. This doesn't seem like the most elegant approach. Its important to preserve as many details as possible from each exception to present to the calling code.
Thoughts?
Edit: The solution must be written in .Net 3.5. I cannot use any beta libraries, or the AggregateException in .Net 4.0 as mentioned by Bradley Grainger (below) would be a nice solution for collection exceptions to throw.
Who says you cannot throw multiple exceptions in one method. If you are not used to playing around with AggregateExceptions you may be tempted to create your own data-structure to represent many things going wrong.
You can't throw two exceptions.
When catching multiple exceptions in a single catch block, the rule is generalized to specialized. This means that if there is a hierarchy of exceptions in the catch block, we can catch the base exception only instead of catching multiple specialized exceptions.
No, we can write multiple catch block but only one is executed at a time. No, multiple catch blocks can't be executed in C#.
The Task Parallel Library extensions for .NET (which will become part of .NET 4.0) follow the pattern suggested in other answers: collecting all exceptions that have been thrown into an AggregateException class.
By always throwing the same type (whether there is one exception from the child work, or many), the calling code that handles the exception is easier to write.
In the .NET 4.0 CTP, AggregateException
has a public constructor (that takes IEnumerable<Exception>
); it may be a good choice for your application.
If you're targeting .NET 3.5, consider cloning the parts of the System.Threading.AggregateException
class that you need in your own code, e.g., some of the constructors and the InnerExceptions property. (You can place your clone in the System.Threading
namespace inside your assembly, which could cause confusion if you exposed it publicly, but will make upgrading to 4.0 easier later on.) When .NET 4.0 is released, you should be able to “upgrade” to the Framework type by deleting the source file containing your clone from your project, changing the project to target the new framework version, and rebuilding. Of course, if you do this, you need to carefully track changes to this class as Microsoft releases new CTPs, so that your code doesn't become incompatible. (For example, this seems like a useful general-purpose class, and they could move it from System.Threading
to System
.) In the worst case, you can just rename the type and move it back into your own namespace (this is very easy with most refactoring tools).
Two ways of the top of my head would be either make a custom exception and add the exceptions to this class and throw that the end :
public class TaskExceptionList : Exception { public List<Exception> TaskExceptions { get; set; } public TaskExceptionList() { TaskExceptions = new List<Exception>(); } } public void DoTasks(MyTask[] taskList) { TaskExceptionList log = new TaskExceptionList(); foreach (MyTask task in taskList) { try { DoTask(task); } catch (Exception ex) { log.TaskExceptions.Add(ex); } } if (log.TaskExceptions.Count > 0) { throw log; } }
or return true or false if the tasks failed and have a 'out List' variable.
public bool TryDoTasks(MyTask[] taskList, out List<Exception> exceptions) { exceptions = new List<Exception>(); foreach (MyTask task in taskList) { try { DoTask(task); } catch (Exception ex) { exceptions.Add(ex); } } if (exceptions.Count > 0) { return false; } else { exceptions = null; return true; } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With