UK Consulting Links
UK Consulting Blogs
I have seen quite a few projects where the code starts with the best of intentions in terms of readability, but soon setup and boilerplate code creep in and it becomes hard to focus on where the setup and clean up ends and the functionality begins. The other downside being repetition of this boilerplate code when doing similar things, such as calling web services, logging, exception handling etc., and being able to manage where this repeated code exists.
With a few examples I hope to provide an idea of what can be done to refactor out any repeated setup and clean up code with the help of the Action and Func delegates.
Let’s say I have a WCF WebService, which I need to setup the environment connection before calling the method, and then have to do some clean up and exception handling afterwards. This may take the form of:
public string GetData(int id){ string result; using (WebServiceClient proxy = new WebServiceClient()) { // do setup... try { result = proxy.GetData(id); } catch (ArgumentNullException ex) { // common exception handling } catch (InvalidOperationException ex) { // common exception handling } catch (...) { // common exception handling } finally { // do cleanup... } } return result;}
If we were to take everything other than the web service call from the code above, then it would be easier to see the intent of this method. If we imagine that GetData isn’t the only method we want to be able to call from this layer, the common code would have to be replicated for each of those calls, making this class a lot noisier.
It is possible to encompass all web calls with 2 overrides of a single helper method, i.e. a web service which returns an object, and those which do not. Utilizing Func (those which return some data) and Action (those which are void), it is possible to create the following helper methods:
public static void CallWebService( Action webServiceToCall, WebServiceClient client){ // do setup... try { webServiceToCall(); } catch (ArgumentNullException ex) { // common exception handling } catch (InvalidOperationException ex) { // common exception handling } catch (...) { // common exception handling } finally { // do cleanup... }}
public static T CallWebService<T>( Func<T> webServiceToCall, WebServiceClient client){ T result; // do setup... try { result = webServiceToCall(); } catch (ArgumentNullException ex) { // common exception handling } catch (InvalidOperationException ex) { // common exception handling } catch (...) { // common exception handling } finally { // do cleanup... } return result;}
I have passed a reference of the WebServiceClient through so that the setup may modify the proxy object before executing it’s method. Similarly for clean up after the method is called.
Now with these in place I can modify my GetData method to look like:
public string GetData(int id){ string result; using (WebServiceClient proxy = new WebServiceClient()) { result = CallWebService<string>( () => proxy.GetData(id), proxy); } return result;}
() => proxy.GetData(id)
and passing the proxy object through so that the CallWebService<T> helper may use it to do setup and clean up operations.
Now if I wish to change my exception handling, I only have to change it in one place instead of everywhere I am handling those types of exceptions.
By further utilizing the DEBUG symbols within visual studio, I can easily modify what is logged between debug and the release builds:
public static T CallWebService<T>( Func<T> webServiceToCall, WebServiceClient client){ T result; // do setup... DEBUG Trace.WriteLine("..."); if try { result = webServiceToCall(); } catch (ArgumentNullException ex) { // common exception handling DEBUG Trace.WriteLine("..."); if } catch (InvalidOperationException ex) { // common exception handling DEBUG Trace.WriteLine("..."); if } catch (...) { // common exception handling DEBUG Trace.WriteLine("..."); if } finally { // do cleanup... DEBUG Trace.WriteLine("..."); if } return result;}
These ideas are not by any means limited to calling web services, tracing or exception handling. Almost anything you do within a repeated fashion which cannot be refactored in the standard ways can be considered for this approach. One place I have found this very useful is within Remote Powershell calls, I have marked the setup and clean up code here with comments to see how much it can take over:
// Start of setup code PSCredential credential = GetCredentials(user, pw);Runspace runspace = GetRunspace(credential); try{ PSCredential readerCred = GetCredentials(readerUser, readerPw); PSObject readingConnection = GetConnection(runspace, readerCred); object connectionSettings; PSPropertyInfo connectionProp = readingConnection.Properties["ConnectionSettings"]; if (connectionProp != null && connectionProp.IsGettable) { connectionSettings = connectionProp.Value; } else { throw new ConnectionException(...); } // End of setup code ExecutePowershellCommand(runspace, connectionSettings, command); // Start of cleanup code }catch (...){ // Exception handling throw;}catch (...){ // Exception handling throw;}finally{ if (runspace != null) { CloseRunspace(runspace); runspace = null; }}// End of cleanup code
Happy coding!
Written by Dave Thompson
FYI, if you didn't already know, these are called higher-order functions: en.wikipedia.org/.../Higher-order_function
One step futher :-)
public static void CallWebService(Action webServiceToCall, WebServiceClient client)
{
CallWebService(() =>
webServiceToCall();
return new Empty();
}, client);
}
private struct Empty { }