views:

228

answers:

8

Hi everyone,

I have the need to do some logging within my code. I'm required to use an internal company-developed library to record some information. Here's how it works.

Recorder recorder = Recorder.StartTiming();
DoSomeWork();
recorder.Stop();  // Writes some diagnostic information.

To ensure that Stop() is always called, I created a wrapper class that allows a clean "using" block.

using (RecorderWrapper recorderWrapper = new RecorderWrapper)  // Automatically calls Recorder.StartTiming() under the covers
{
   DoSomeWork();
}  // When the recorderWrapper goes out of scope, the 'using' statement calls recorderWrapper.Dispose() automatically - which calls recorder.Stop() under the covers

it's worked well so far. However, there's a change my company is requiring, that would look something like this on the original code:

Recorder recorder = Recorder.StartTiming();
try
{
   DoSomeWork();
}
catch (Exception ex)
{
   recorder.ReportFailure(ex);  // Write out some exception details associated with this "transaction"
}
recorder.Stop();  // Writes some diagnostic information.

I'd like to avoid try/catches in all my 'using' scope blocks with RecorderWrapper. Is there a way I can accomodate the "ReportFailure()" call and still leverage the 'using' scope block?

Specifically, I want everyone on my team to "fall into a pit of success", i.e. make it easy to do the right thing. To me, this means making it really hard to forget to call recorder.Stop() or forget the try/catch.

Thanks!

A: 

You could copy the pattern used by TransactionScope, and write a wrapper that must be actively completed - if you don't call Complete(), then the Dispose() method (which gets called either way) assumes an exception and does your handling code:

using(Recorder recorder = Recorder.StartTiming()) {
    DoSomeWork();
    recorder.Complete();
}

Personally, though, I'd stick with try/catch - it is clearer for maintainers in the future - and it provides access to the Exception.

Marc Gravell
+1  A: 

No, a using block is only syntactic sugar for a try/finally block. It doesn't deal with try/catch. At that point you're going to be left with handling it yourself since it looks like you need the exception for logging purposes.

Joseph
+1  A: 

A using block is effectively a try/finally block that calls dispose on the object in question.

So, this:

using(a = new A())
{
    a.Act();
}

is (i think, exactly) equivalent to this:

a = new A();
try
{
    a.Act();
}
finally
{
    a.Dispose();
}

And you can tack your catches onto the end of the try block.

Edit:

As an alternative to Rob's solution:

Recorder recorder = Recorder.StartNew()
try
{
    DoSomeWork();
}
catch (Exception ex)
{
    recorder.ReportFailure(ex);
}
finally
{
    recorder.Dispose();
}
John Gietzen
What you say is true, but that avoids the question I'm trying to ask.
Mike
The answer is: no, the only way to have a `catch` block is to have a `try` block.
John Gietzen
It's not *exactly* equivalent, although close. (See section 8.13 of the C# specification.) There is a block around the entire thing, `a` is checked for `null` (if `A` is a reference type), and `a` is cast to `IDisposable` before calling `Dispose()` (I presume that is done to get around member hiding and to have it work with explicit interface implementations).
Joren
@Joren: Thanks for clarifying.
John Gietzen
A: 

Don't add another level of indirection. If you need to catch the Exception, use try..catch..finally and call Dispose() in the finally block.

Wim Hollebrandse
+5  A: 

You might be able to create a method on the recorder to hide this:

public void Record(Action act)
{
    try
    {
        this.StartTiming();
        act();
    }
    catch(Exception ex)
    {
        this.ReportFailure(ex);
    }
    finally
    {
        this.Stop();
    }
}

So your example would then just be:

recorder.Record(DoSomeWork);
Lee
I like this answer too - I added the `finally` block to make it safer though.
280Z28
And if you can't change the recorder there's always the extension method
Rune FS
Yeah, this is what I really want
Mike
+3  A: 

You could always try something like:

Edit by 280Z28: I'm using a static StartNew() method here similar to Stopwatch.StartNew(). Make your Recorder class IDisposable, and call Stop() from Dispose(). I don't think it gets any more clear than this.

using (Recorder recorder = Recorder.StartNew())
{
    try
    {
        DoSomeWork();
    }
    catch (Exception ex)
    {
        recorder.ReportFailure(ex);
    }
}
Rob Pelletier
This is exactly how you handle it - reformatted it to make it clear what's going on though.
280Z28
Thanks. I purposely left out the braces in the using block to highlight how close it was to what the original poster was trying to accomplish.
Rob Pelletier
+1 - never seen that before! and it compiles! nifty! wait a second! reformatted, looks like my answer! d'oh! was just regular block nesting. you still keep the +1 though ;)
johnny g
@Rob: Unfortunately it became completely unreadable IMO. I updated it to use a `StartNew` method since the .NET Framework has a similarly named function, and the consistency makes things easier to understand.
280Z28
+1 for a short and simple answer. Rob got it dead on
Luke101
How is different that a try/catch/finally?
John Gietzen
@John, it *is* a try/catch/finally, except it's clear that the recorder is a resource separate from and wrapping the work.
280Z28
Added comments to the question explaining why I want to avoid lots of try/catches."Specifically, I want everyone on my team to "fall into a pit of success", i.e. make it easy to do the right thing. To me, this means making it really hard to forget to call recorder.Stop() or forget the try/catch."
Mike
+3  A: 

You could continue to use the RecorderWrapper you have, but add a TryExecuting method that accepts a lambda of what you want to happen add runs it in a try/catch block. eg:

using (RecorderWrapper recorderWrapper = new RecorderWrapper)  // Automatically calls Recorder.StartTiming() under the covers
{
    recorderWrapper.TryExecuting(() => DoSomeWork());
}

Inside RecorderWrapper:

public void TryExecuting(Action work)
{
    try { work(); }
    catch(Exception ex) { this.ReportFailure(ex); }
}
Chris Shaffer
+1  A: 

Oops, I hadn't noticed that a new instance of Recorder was being created by StartTiming. I've updated the code to account for this. The Wrap function now no longer takes a Recorder parameter but instead passes the recorder it creates as an argument to the action delegate passed in by the caller so that the caller can make use of it if needed.

Hmmm, I've needed to do something very similar to this pattern, lambdas, the Action delegate and closures make it easy:

First define a class to do the wrapping:

public static class RecorderScope
{
   public static void Wrap(Action<Recorder> action)
   {
      Recorder recorder = Recorder.StartTiming();
      try
      {
         action(recorder);
      }
      catch(Exception exception)
      {
         recorder.ReportFailure(exception);
      }
      finally
      {
         recorder.Stop();
      }
   }
}

Now, use like so:

RecorderScope.Wrap(
   (recorder) =>
   {
      // note, the recorder is passed in here so you can use it if needed -
      // if you never need it you can remove it from the Wrap function.
      DoSomeWork();
   });

One question though - is it really desired that the catch handler swallows the exception without rethrowing it? This would usually be a bad practice.

BTW, I'll throw in an addition to this pattern which can be useful. Although, it doesn't sound like it applies to what you're doing in this instance: Ever wanted to do something like the above where you want to wrap some code with a set of startup actions and completion actions but you also need to be able to code some specific exception handling code. Well, if you change the Wrap function to also take an Action delegate and constrain T to Exception, then you've got a wrapper which allows user to specify the exception type to catch, and the code to execute to handle it, e.g.:

public static class RecorderScope
{
   public static void Wrap(Action<Recorder> action, 
      Action<Recorder, T1> exHandler1)
      where T1: Exception
   {
      Recorder recorder = Recorder.StartTiming();
      try
      {
         action(recorder);
      }
      catch(T1 ex1)
      {
         exHandler1(recorder, ex1);
      }
      finally
      {
         recorder.Stop();
      }
   }
}

To use.. (Note you have to specify the type of exception, as it obviously cannot be inferred. Which is what you want):

RecorderScope.Wrap(
   (recorder) =>
   {
      DoSomeWork();
   },
   (recorder, MyException ex) =>
   {
      recorder.ReportFailure(exception);
   });

You can then extend this pattern by providing multiple overloads of the Wrap function which take more than one exception handler delegate. Usually five overloads will be sufficient - it's pretty unusual for you to need to catch more than five different types of exceptions at once.

Phil