【问题标题】:Combining Action and Func in one parameter将 Action 和 Func 结合在一个参数中
【发布时间】:2013-04-04 14:23:32
【问题描述】:

我有许多方法需要使用相同模式进行一些日志记录。有些方法需要返回一些值,有些则不需要。我创建了一个带有 Action 参数的方法,以避免复制粘贴所有逻辑。它看起来像这样:

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

现在我有一些类似的电话

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

但我需要一些返回值的函数。最好的方法是什么? 我现在找到了两个:

1) 使用 Func 创建 Execute 方法的副本

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

此方法有效,但也有一些复制粘贴。

2) 欺骗参数成为一个动作:

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

这不需要复制粘贴,但看起来不太好。

有没有办法以某种方式加入 Action 和 Func 以避免这些方法中的任何一种,或者可能有另一种方法来实现相同的结果?

【问题讨论】:

  • 我使用您的第二种方法。我找不到任何其他方式的好方法,所以我很想看看你的问题的任何答案!
  • 好像是相关的:stackoverflow.com/q/4279210/55209
  • 你能把这个模式(Logger.Start、try/catch/finally、Logger.WriteException、Logger.Finish)放到你的Logger类本身吗?那时,代码重复的数量通常相当少,足以将Func&lt;T&gt; 包装为Action 与闭包语义并不是真正必要的。我怀疑您希望日志记录的处理开销尽可能小。 (或者,谁在乎?过早的优化!)但我肯定会考虑将Execute 重载放入Logger 本身。编辑:我猜Logger 不是线程安全的,但这不是问题,嗯?

标签: c# .net delegates action func


【解决方案1】:

第三种选择仍然是重载Execute,但使Action 版本根据Func 版本工作:

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

当然,如果 void 更像一个“真实”类型(如 F# 等中的 Unit),那么这一切都会更简单,此时我们可以只使用 Task&lt;T&gt; 而不是 Task 和 @ 987654329@也...

【讨论】:

  • 我也想到了这个方法,但甚至没有包含它,因为它看起来很脏,因为您通过使用 int 作为泛型类型并返回永远不会返回的默认值来假装是 Func用过。
  • @IlyaChernomordik:我认为这比分配给 Action 中的局部变量以使其表现得像 Func&lt;T&gt; 个人而言更干净。但关键是它在Execute 内,而不是在DoSomething 内——你只需要在一个地方而不是多个调用者中需要这个“污垢”。
  • @JonSkeet 也许你是对的,而且它不那么脏。或者可能同样脏但在一个地方:)
【解决方案2】:

创建Execute 的副本,将Func 转换为Action。你只需要写一次丑陋的代码,你不会得到Execute方法的完整的第二个副本:

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());
}

【讨论】:

  • @ p.s.w.g 这种方法的问题是我有值类型和类作为返回。而且你的代码不会编译,因为局部变量在返回之前没有初始化。
  • @IlyaChernomordik 只需将result 初始化为default(T)。请参阅我的更新答案。
  • @ p.s.w.g 对,没有考虑默认的。实际上这个更新对于我的第二种方法来说是非常合乎逻辑的,不知道为什么我没有考虑过。
  • @ p.s.w.g 顺便说一句,return 应该在 Execute 之前,在你的示例中似乎(DoSomething 函数)
  • @IlyaChernomordik 哎呀,你是对的。是的default(T) 是一个非常方便的小技巧。
【解决方案3】:

这是另一种选择。不要让日志框架调用您的实际代码,而是让您的实际代码调用日志框架。这样的事情就可以解决问题(大大简化了)。

public class LoggerScope : IDisposable {

    private bool disposed;

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

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

如下使用:

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

通过在代码的顶层仅捕获和记录一次异常来单独处理异常。

优点:

  • 避免到处都需要 lambda,因此异常堆栈跟踪更加清晰。
  • 您可以将任意上下文数据添加到 LoggerScope 类,例如GUID、时间戳、逻辑任务描述文本。

【讨论】:

  • 缺点是你必须为每个特殊情况创建一个特殊的类。虽然我已经使用这种方法来进行读写器锁定。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-14
  • 1970-01-01
  • 2022-01-23
  • 2023-03-25
  • 1970-01-01
  • 2011-05-02
相关资源
最近更新 更多