Re: Abstracting Away From Exceptions

I was going to comment on this post, but ended up writing way too much so I figure I’ll post my thoughts here instead.

In the comments, Hristo Deshev suggested making the method generic rather than the class. I figure, why not make the exception generic as well?

public static class Adverse
{
  public static TResult Call<TResult, TException>(Func<TResult> attempt,
                                                  Func<TException, TResult> recover
                                                 ) where TException : Exception
  {
    try { return attempt(); }
    catch (TException e) { return recover(e); }
  }
}

Regarding Aaron Powell’s concern about not wanting to handle all exceptions the same, you can always rethrow an exception you don’t recognize:

var res = Adverse.Call(() => { return ""; },
  (ArgumentException ae) =>
  {
    if (ae is ArgumentNullException)
      return "Null";
    else if (ae is ArgumentOutOfRangeException)
      return "OOR";
    else
      throw ae;
  });

And finally, a few comments pointed out that a method that doesn’t accept arguments isn’t really a function. So I set out to add support for an argument. Here is my first attempt:

public static TResult Call<TArg, TResult, TException>(TArg arg,
                                                      Func<TArg, TResult> attempt,
                                                      Func<TArg, TException, TResult> recover
                                                     ) where TException : Exception
{
    try { return attempt(arg); }
    catch (TException e) { return recover(arg, e); }
}

It gets the job done, but I found the usage rather awkward:

var r1 = Call(5,
    i => string.Format("{0} squared is {1}", i, i * i),
    (int i, ArithmeticException e) => string.Format("Could not square {0}", i));

This got me thinking about how F# supports currying and partial function application, in other words functions that return functions with fewer arguments. Applied here, we can write a method that returns a function from TArg to TResult:

public static Func<TArg, TResult> Call<TArg, TResult, TException>(Func<TArg, TResult> attempt,
                                                                  Func<TArg, TException, TResult> recover
                                                                 ) where TException : Exception
{
    return (arg) =>
    {
        try { return attempt(arg); }
        catch (TException e) { return recover(arg, e); }
    };
}

So instead of accepting an argument in Call(), we pass the argument into the function it returns:

var snd = Call(s => string.Format("The second character of {0} is {1}", s, s[1]),
    (string s, IndexOutOfRangeException e) => string.Format("{0} has no 2nd character", s));

foreach (string s in new string[] { "ab", "c", "de" })
    Console.WriteLine(snd(s));

Or an argument can be passed directly:

var str = Call(o => o.ToString(), (object o, NullReferenceException e) => "(null)")(myObj);

This could easily be extended to support functions with multiple arguments.

I’m not sure if I’ll ever get around to using this pattern, but Vesa Karvonen provides good justification for doing so:

…a try-catch statement has to produce a result via a side-effect, because it doesn’t return any result (it returns unit/void). For example, it has to update a variable declared outside of the try-catch statement. Whether or not both the tried statement and the handler statement update the variable is not apparent at the type level.

Thoughts?

Update 1/11/2009:

Paul suggests using a params array of “recover” delegates to handle different Exception types. I looked into it at first, but without covariance the delegates couldn’t be strongly typed (can’t assign Func<string, ArgumentException, bool> to Func<string, Exception, bool>). And if they’re all of type Func<string, Exception, bool>, how do we pick which one to use?

I don’t have VS 2010, but I would think C# 4.0 could resolve this, assuming Func<T1, T2, TResult> is redefined as Func<in T1, in T2, out TResult>.

With covariance, we could do something like this:

public static Func<TArg, TResult> Call<TArg, TResult>(Func<TArg, TResult> attempt,
                                                      params Func<TArg, Exception, TResult>[] recover)
{
  return (arg) =>
  {
    try { return attempt(arg); }
    catch (Exception e)
    {
      foreach(var r in recover)
      {
        Type rType = r.GetType().GetGenericArguments()[1];
        if (rType.IsInstanceOfType(e))
          return (TResult)r.DynamicInvoke(arg, e);
      }
      throw e;
    }
  };
}

As with try…catch, more specific types would need to be listed first. It would be great if someone with access to C# 4.0 could give this a try…

Advertisements

6 Responses to “Re: Abstracting Away From Exceptions”

  1. Paul Stovell Says:

    You could also make “recover” a params array, to handle different types, eg:

    Call(s => blah,
    (s, ArgumentException) => “Argument exception!”,
    (s, NullReferenceException) => “NullRef exception!”
    );

  2. Keith Dahlby Says:

    Paul ~

    Thanks for the comment. I’ve posted my response at the end of the post so I could make it pretty. :)

    Cheers ~
    Keith

  3. Aaron Powell Says:

    Good post Keith, but I think that Paul’s addition really makes it work sweetly.

    Just catching (like your example) ArgumentException is great if the expected exceptions are of that type, but take SmtpClient.Send. There’s half a dozen exceptions which can be thrown there. You’d have to catch System.Exception and type check each one.

  4. Keith Dahlby Says:

    Hey Aaron ~

    ArgumentException was just an example – you could certain catch and rethrow using Exception as well. It’s not ideal, but I think it’s the best we can do for now.

    I agree that Paul’s approach has merit, it’s just not possible to implement elegantly (using only delegates) without covariance. If we try it in C# 3, all our delegates have to be Func<TArg, Exception, TResult> which has two disadvantages:
    1) In order to do strongly-typed exception filtering, we would need to pass type/delegate pairs.
    2) Within the delegate we would need to cast the Exception back to its paired type, making for some ugly code.

    I just got the VS 10 VPC downloaded only to find the VS eval has expired, so testing my covariant implementation will have to wait. Do you have any ideas to make it work in C# 3?

    Keith

  5. Aaron Powell Says:

    This will fix your CTP expiry – http://blogs.msdn.com/jeffbe/archive/2008/12/09/dealing-with-the-team-system-2010-ctp-expiration.aspx

    I’ll have to chuck the code into VS and have a play to see if I can get my head around it though

  6. Keith Dahlby Says:

    Thanks for the expiry tip.

    So only after I started playing in C# 4.0 did I realize that we can only have covariance on the return type. For example, we shouldn’t be able to do this:

    Func<ArgumentException, string> AE = e => e.ParamName;
    Func<Exception, string> E = AE;
    E(new NullReferenceException());

    AE expects an ArgumentException, but E gives it a NullReferenceException that doesn’t have a ParamName property.

    So as pretty as Paul’s approach would be, it can’t work quite that nicely. The Type/Delegate fallback works, varied slightly from my supposedly covariant version, but the usage isn’t pretty:

    struct Rec<T> {
    public Type ExceptionType;
    public Func<Exception, T> Lambda;
    public Rec(Type et, Func<Exception, T> l) {
    ExceptionType = et;
    Lambda = l;
    }
    }

    var res = Call<string>(ExTestMethod,
    new Rec<string>(typeof(ArgumentNullException),
    (Exception e) => e.GetType().ToString()),
    new Rec<string>(typeof(ArgumentException),
    (Exception e) => ((ArgumentException)e).ParamName)
    );


Comments are closed.

%d bloggers like this: