Ilya Chernomordik
Ilya Chernomordik

Reputation: 30205

Combining Action and Func in one parameter

I have many methods that require some logging with the same pattern. Some methods need to return some value, some don't. I have created a method with Action parameter to avoid copypasting all of the logic. It looks like this:

private void Execute(Action action)
{
   Logger.Start();
   try
   {
      action();
   }
   catch(Exception exception)
   {
      Logger.WriteException();
      throw;
   }
   finally
   {
       Logger.Finish();
   }
}

Now I have some calls that like that

public void DoSomething(string parameter)
{
    Execute(() => GetProvider(parameter).DoSomething());
}

But I need some function that return values. What are the best ways to do it? I have found two now:

1) Create a copy of Execute method with Func

private T Execute<T>(Func<T> action)
{
   Logger.Start();
   try
   {
      return action();
   }
   catch(Exception exception)
   {
      Logger.WriteException();
      throw;
   }
   finally
   {
       Logger.Finish();
   }
}

This method works but has some copy paste as well.

2) Trick the parameter into being an Action:

public Result DoSomething(string parameter)
{
    Result result = null;
    Execute(() => result = GetProvider(parameter).DoSomething());
    return result;
}

This does not require copy paste but does not look so nice.

Is there a way to join Action and Func somehow to avoid any of these methods or may be there is another way to achieve the same result?

Upvotes: 8

Views: 1393

Answers (3)

p.s.w.g
p.s.w.g

Reputation: 149020

Create a copy of Execute that converts the Func into an Action. You only have to write that ugly code once, and you don't end up with a complete second copy of the Execute method:

private T Execute<T>(Func<T> func)
{
    T result = default(T);
    this.Execute(() => { result = func(); });
    return result;
}

...

public Result DoSomething(string parameter)
{
    return Execute(() => GetProvider(parameter).DoSomething());
}

Upvotes: 3

Christian Hayter
Christian Hayter

Reputation: 31071

Here's another option. Instead of having the logging framework call your actual code, have your actual code call the logging framework. Something like this would do the trick (greatly simplified).

public class LoggerScope : IDisposable {

    private bool disposed;

    public LoggerScope() {
        Logger.Start();
    }

    public void Dispose() {
        if(!disposed) {
            Logger.Finish();
            disposed = true;
        }
    }
}

Used as follows:

        using(var scope = new LoggerScope()) {
            // actual code goes here
        }

Handle exceptions separately by catching and logging them only once at the top level of your code.

Advantages:

  • Avoids the need for lambdas all over the place, so the exception stack trace is a bit cleaner.
  • You can add arbitrary contextual data to the LoggerScope class, e.g. GUID, timestamp, logical task description text.

Upvotes: 2

Jon Skeet
Jon Skeet

Reputation: 1500385

A third option is to still overload Execute, but make the Action version work in terms of the Func version:

private void Execute(Action action)
{
    // We just ignore the return value here
    Execute(() => { 
        action();
        return 0; 
    });
}

Of course all this would be simpler if void were more like a "real" type (like Unit in F# et al), at which point we could just have Task<T> instead of Task and Task<T> as well...

Upvotes: 7

Related Questions