Tuesday, March 18, 2014

Using Lamda's and Generics to encapsulate a Try-Catch-Finally block

I recently had a legacy (.NET 2.0) project that I was asked to bring up to date with .NET 4.5.  In the process I came across numerous functions that followed the same format wrapping their functionality in a try-catch-finally code block or some variant (try-catch or try-finally).

This is a normal process in many apps, of course, but in many of these cases the catch and finally logic were duplicated.  The more I worked with it, the more I felt like there had to be a way to create a re-usable function to handle it for me.  What I came up with works pretty darn well, though it does make the code a little more complicated.

Code Snippet
  1. public static TResult TryCatch<TParam1, TResult, TException>(this Func<TParam1[], TResult> executeFunc, Func<TException, TResult> errorAction = null, Action<TParam1[]> finalAction = null, params TParam1[] args) where TException : Exception
  2. {
  3.     if (executeFunc == null)
  4.         throw new ArgumentNullException("executeFunc");
  5.  
  6.     try
  7.     {
  8.         return executeFunc.Invoke(args);
  9.     }
  10.     catch (TException ex)
  11.     {
  12.         if (errorAction != null)
  13.             return errorAction.Invoke(ex);
  14.     }
  15.     finally
  16.     {
  17.         if (finalAction != null)
  18.             finalAction.Invoke(args);
  19.     }
  20.     throw new ArgumentException("No return value specified");
  21. }

As you can see the function takes a variety of inputs to handle the guts of the code itself as well as the catch and finally blocks. The key parts of this function are:

  1. TParam1 - The type of parameter object
  2. TResult - The type of the return object
  3. TException - The type of exception expected to be caught
  4. executeFunc - The function to execute in the try block
  5. errorAction - An action to perform if an exception of the given type is caught
  6. finalAction - An action to perform on Finally
  7. args - An array of arguments passed to the executeFunc (if any)

Code Snippet
  1. [Test]
  2. public void TryCatch_Test()
  3. {
  4.     var callback = false;
  5.  
  6.     Func<object[], bool> func = x =>
  7.         {
  8.             callback = true;
  9.             return true;
  10.         };
  11.     Func<Exception, bool> showError = exception => false;
  12.  
  13.     var result = func.TryCatch(showError);
  14.  
  15.     Assert.That(result, Is.True);
  16.     Assert.That(callback, Is.True);
  17. }
  18.  
  19. [Test]
  20. public void TryCatch_Exception_Test()
  21. {
  22.     var callback = false;
  23.  
  24.     Func<object[], bool> func = x => { throw new Exception("Test Exception"); };
  25.  
  26.     Func<Exception, bool> showError = exception =>
  27.         {
  28.             callback = true;
  29.             return false;
  30.         };
  31.  
  32.     var result = func.TryCatch(showError);
  33.  
  34.     Assert.That(result, Is.False);
  35.     Assert.That(callback, Is.True);
  36. }
  37.  
  38. [Test]
  39. public void TryCatch_DifferentType_Test()
  40. {
  41.     var callback = false;
  42.     var finalCallback = false;
  43.  
  44.     Func<int[], int> func = ints =>
  45.         {
  46.             callback = true;
  47.             return ints.Sum();
  48.         };
  49.     Func<Exception, int> showError = exception => 0;
  50.     Action<int[]> final = ints => { finalCallback = true; };
  51.  
  52.     var result = func.TryCatch(showError, final, 2, 5, 8);
  53.  
  54.     Assert.That(result, Is.EqualTo(15));
  55.     Assert.That(callback, Is.True);
  56.     Assert.That(finalCallback, Is.True);
  57. }

I included these three unit tests as I felt like they displayed the uses of this function quite nicely.

This code creates a nice re-usable function that gives us some real power and flexibility. No need to wrap your functions into a try-catch block. And since its an extension method you can write your Func and then tack the .TryCatch() call to the end.

No comments :

Post a Comment