【问题标题】:Is it possible to extend the 'using' block in C#?是否可以在 C# 中扩展“使用”块?
【发布时间】:2017-07-19 12:17:37
【问题描述】:

有没有办法在 C# 中扩展 using 块,将委托作为 IDisposable 对象旁边的第二个参数,并在每次在 using 块内引发异常时执行?

假设我们有一个委托,如下所示:

public delegate void ExceptionHandler(Exception ex);

假设我有一个与该委托匹配的方法,如下所示:

public void Log(Exception ex)
{
  // Some logging stuff goes here
}

我想完成这样的事情:

using(SqlConnection connection = new SqlConnection(""), Log)
{

}

有没有办法以这种方式扩展 C#?

【问题讨论】:

  • 为什么不将 using 块封装在 try 块中并在抛出异常时调用 Log?
  • 你有理由不在 using 块内使用 try-catch 块吗?
  • @TomaszJuszczak 在不调用 Dispose() 的情况下,无论如何都无法捕获和记录异常,因此这并不重要。
  • 一个完整的旁白:Python 的context managers 可以满足您的需求。 __exit__ 函数无论是否有错误都会被调用,但如果发生错误,错误会作为参数传递给它。这意味着上下文管理器不仅仅充当try,finally 块;它们还可以用于封装except 块的功能。
  • 感谢您的回答。我刚刚在 python 中探索过了。太好了。我希望这种功能会在未来的版本中添加到 C# 中:)

标签: c# .net using-statement


【解决方案1】:

using 块是 try finally 块的简写,在 finally 中调用 Dispose。它不能扩展到更多的东西。你想要的是try catch finally 的功能,所以为什么不完全使用它:

SqlConnection connection = new SqlConnection("");
try {

}
catch (Exception exc) {
    Log(exc);
}
finally {
    connection.Dispose();
}

这具有 try catch finally 的所有优点,例如捕获多种异常类型和 C# 6.0 异常过滤器。考虑一下:

SqlConnection connection = new SqlConnection("");
try {

}
catch (SqlException exc) when (exc.Number > 0) {
    //Handle SQL error
}
catch (Exception exc) {
    Log(exc);
}
finally {
    connection.Dispose();
}

如果你想重用标准化的try catch finally 块,你可以使用委托:

static class ErrorHandler {
    public static ExecuteWithErrorHandling<T>(Func<T> createObject,
        Action<Exception> exceptionHandler, Action<T> operation) where T : IDisposable {

        T disposable = createObject();
        try {
            operation(disposable);
        }
        catch (Exception exc) {
            exceptionHandler(exc);
        }
        finally {
            disposable.Dispose();
        }
    }
}

你可以这样称呼:

ErrorHandler.ExecuteWithErrorHandling(() => new SqlConnection(""), Log, connection => {
    //Use connection here
});

【讨论】:

  • 感谢您的回答。但我正在寻找 C# 中是否有任何扩展点。 using 语句将转换为您在答案中编写的代码。但是我想实现一些事情,当我使用 using 块时,我的 Log 方法在幕后的 catch 块中被调用。
  • C# 中没有该类型的扩展点。您的解决方案也将无法满足现有 try catch finally 的功能(请查看我的最新编辑)。您唯一能做的就是将整个 try catch finally 包装在自定义方法中。
  • 我同意你的观点,但是当我们的代码中只有一个 catch 块时,这将是一种解决方案。我希望在未来的版本中将此功能添加到 C# 中。
  • 既然try catch finally 已经做到了,甚至不止于此,我认为我们永远不会看到这种情况发生。
【解决方案2】:

您不能扩展 using 语句,但可以将其包装在方法中:

void DoStuff(Action action, ExceptionHandler log)
{
    using(var connction = new SqlConnection(""))
    {
        try
        {
            action();
        }
        catch(Exception e)
        {
            log(e)
        }
    }
}

【讨论】:

  • 将其包装在 try...catch 中可能比方法更有用。
  • 实际上,我会将 try...catch 包装在 using... 编辑我的答案以反映这一点。
【解决方案3】:

在语法糖上稍微退后一点。

自:

using(var obj = factory_or_constructor())
{
  // Do Stuff
}

是通用模式的简写

obj = factory_or_constructor();
try
{
  // Do Stuff
}
finally
{
  ((IDisposable)obj)?.Dispose();
}

然后你可以把它改成:

try
{
  // Do Stuff
}
catch(Exception ex)
{
  Log(ex);
  throw;
}
finally
{
  ((IDisposable)obj)?.Dispose();
}

但它并没有真正提供比更简单和更清晰的更多。

using(var obj = factory_or_constructor())
{
  try
  {
    // Do Stuff
  }
  catch(Exception ex)
  {
    Log(ex);
    throw;
  }
}

这并不是真正的“扩展使用”,但如果using 的目的是为常见模式提供简洁的语法,那么为稀有模式提供简洁的语法就没那么有用了。

【讨论】:

  • @xanatos 是的,尽管我不清楚要求是否应该这样做。
  • 对新手的解释很好,但有两个小问题:首先,不是((IDisposable)obj)?.Dispose();,而是(obj as IDisposable)?.Dispose();,否则你会得到castexceptions(是的,我知道using需要IDisposable,但是扩展形式没有,所以至少 第三个 代码示例应该得到这个as 形式);其次,在最后一个例子中,我宁愿写try/using/catch 而不是using/try/catch 像你现在写的那样,因为factory or constructor 还有 dispose 可以抛出,我们可能希望记录它也是。
  • @quetzalcoatl 我想我更喜欢这里的(IDisposable) 演员,因为在这种情况下它会失败,尽管as 实际上更接近生成的代码(并且经常完全优化) .从问题中不清楚是否应该记录处置中的失败,所以我不知道应该把它放在哪里来提供要求。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-16
  • 1970-01-01
  • 2021-10-19
  • 2012-05-15
  • 1970-01-01
相关资源
最近更新 更多