Continuing on from yesterday’s post about simplifying exception handling code by using lambdas and continuation-passing style, I’d like to show off some other examples. Another common pattern in code is to generate a value, test it against some condition, and then have an if/else block to execute two other blocks of code dependent on that test.

So, let’s introduce the TryIgnore() and GenerateTest() helper functions with an example (see below for the trivial implementation). I want to generate a full file path for a log file, delete it if it exists, and then enable logging into that file for the later Windows Installer stuff that will be happening. However, if any of that fails I do not want an exception to be propagated because it isn’t critical to the success of what I’m doing.

Here’s the old code: (I’m omitting the code that initializes the tempPath, dwMsiLogMode, and dwMsiLogAttributes values. Assume that tempPath is just "%TEMP%", and the others aren’t really important for this discussion)

const string logFileName = "PdnMsiInstall.log";

try
{
    string logFilePath = Path.Combine(tempPath, logFileName);

    if (File.Exists(logFilePath))
    {
        File.Delete(logFilePath);
    }
}

catch (Exception)
{
    // Ignore error
}

try
{
    NativeMethods.MsiEnableLogW(
        dwMsiLogMode,
        logFilePath,
        dwMsiLogAttributes);
}

catch (Exception)
{
    // Ignore error
}

And the new code, which makes use of the TryIgnore() and GenerateTest() helper methods:

const string logFileName = "PdnMsiInstall.log";

Do.TryIgnore(() =>
    Do.GenerateTest(() =>
        Path.Combine(tempPath, logFileName),
        s => File.Exists(s),
        s => File.Delete(s),
        s => { /* no-op */ }));

Do.TryIgnore(() => NativeMethods.MsiEnableLogW(dwMsiLogMode, logFilePath, dwMsiLogAttributes));

There’s room to make the code even more succinct by inlining the value of logFileName into the Path.Combine() call, and to have an overload of GenerateTest() that doesn’t take an "ifFalse" delegate. Honestly though, I prefer to have my constants sitting in named values. It makes things easier when you’re sitting in the debugger and scratching your head and/or chin.

Here is the full code for my current "Do" class. I hereby release this into the public domain, so do whatever you want with it. There are obviously many other helper functions that could be added, but what I have her is sufficient to get things started. I’ll let you add others as you need them. And remember, my definition of Function has the type parameters in the reverse order that .NET 3.5’s Func delegate has them.

public delegate R Function<R>();
public delegate R Function<R, TArg>(TArg arg);
public delegate void Procedure();
public delegate void Procedure<TArg>(TArg arg);

public static class Do
{
    public static void GenerateTest<T>(
        Function<T> generate,
        Function<bool, T> test,
        Procedure<T> ifTrue,
        Procedure<T> ifFalse)
    {
        T value = generate();
        (test(value) ? ifTrue : ifFalse)(value);
    }

    public static bool TryBool(Procedure actionProcedure)
    {
        try
        {
            actionProcedure();
            return true;
        }

        catch (Exception)
        {
            return false;
        }
    }

    public static Exception TryEx(Procedure actionProcedure)
    {
        try
        {
            actionProcedure();
            return null;
        }

        catch (Exception ex)
        {
            return ex;
        }
    }

    public static void TryIgnore(Procedure actionProcedure)
    {
        try
        {
            actionProcedure();
        }

        catch (Exception)
        {
            // Ignore
        }
    }

    public static void TryCatch(
        Procedure actionProcedure,
        Procedure<Exception> catchClause)
    {
        try
        {
            actionProcedure();

        }

        catch (Exception ex)
        {
            catchClause(ex);
        }
    }

    public static T TryCatch<T>(
        Function<T> actionFunction,
        Function<T, Exception> catchClause)
    {
        T returnVal;

        try
        {
            returnVal = actionFunction();
        }

        catch (Exception ex)
        {
            returnVal = catchClause(ex);
        }

        return returnVal;
    }

    public static void TryCatchFinally(
        Procedure actionProcedure,
        Procedure<Exception> catchClause,
        Procedure finallyClause)
    {
        try
        {
            actionProcedure();
        }

        catch (Exception ex)
        {
            catchClause(ex);
        }

        finally
        {
            finallyClause();
        }
    }

    public static T TryCatchFinally<T>(
        Function<T> actionFunction,
        Function<T, Exception> catchClause,
        Procedure finallyClause)
    {
        T returnVal;

        try
        {
            returnVal = actionFunction();
        }

        catch (Exception ex)
        {
            returnVal = catchClause(ex);
        }

        finally
        {
            finallyClause();
        }

        return returnVal;
    }

    public static void TryFinally(
        Procedure actionProcedure,
        Procedure finallyClause)
    {
        try
        {
            actionProcedure();
        }

        finally
        {
            finallyClause();
        }
    }
}

Advertisements