从using block内部返回是可以的吗?

本文关键字:using block 内部 返回 | 更新日期: 2023-09-27 17:49:30

我正在做代码审查,并发现许多代码具有以下格式:

public MyResponse MyMethod(string arg)
{
    using (Tracer myTracer = new Tracer(Constants.TraceLog))
    {
        MyResponse abc = new MyResponse();
        // Some code
        return abc;
    }
}

当我运行代码分析时,我得到CA2000警告Microsoft。可靠性

代码应该重写为:

public MyResponse MyMethod(string arg)
{
   MyResponse abc = new MyResponse();
   using (Tracer myTracer = new Tracer(Constants.TraceLog))
   {
       // Some code
   }
   return abc;
}

还是无所谓?

编辑

报告警告的那行是:

MyResponse abc = new MyResponse();

MyResponse是一个标准数据集。

完整的错误信息是:

警告150 CA2000: Microsoft。可靠性:在方法'xxxxx(Guid, Guid)'中,对象'MyResponse '不会沿着所有异常路径进行处理。在对象'MyResponse '的所有引用都超出作用域之前,调用System.IDisposable.Dispose。

从using block内部返回是可以的吗?

不,没关系。

using语句隐式生成的finally块将执行,无论你把return放在哪里。

你确定CA2000涉及myTracer而不是abc吗?我猜发生警告是因为MyResponse实现了IDisposable,而您在返回之前没有处理abc。(无论哪种方式,您建议的重写都不会对警告产生影响。)

您的重写将无法修复CA2000警告,因为问题不是Tracer对象,而是MyResponse对象。
文档说明:

以下是using语句不足以保护可丢弃对象并可能导致CA2000发生的一些情况。
返回一个一次性对象要求该对象是在using块之外的try/finally块中构造的。

要修复警告而不干扰异常的堆栈跟踪(<-单击,这是一个链接),使用以下代码:
public MyResponse MyMethod(string arg)
{
   MyResponse tmpResponse = null;
   MyResponse response = null;
   try
   {
       tmpResponse = new MyResponse();
       using (Tracer myTracer = new Tracer(Constants.TraceLog))
       {
           // Some code
       }
       response = tmpResponse;
       tmpResponse = null;
    }
    finally
    {
        if(tmpResponse != null)
            tmpResponse .Dispose();
    }
    return response;
}

为什么?

这个警告可能是关于MyResponse的,也就是IDisposable

为什么会出现警告?

如果构造了一个MyResponse对象,但是方法后面的代码导致抛出异常,那么对该对象的所有引用都将丢失(我们只有一个,并且没有设法返回它)。这意味着不能再在对象上调用Dispose,我们将依靠类终结器来清理所有资源。

有关系吗?

一般来说,只有当:

  • IDisposable封装了可能被程序的其他部分或另一个进程"很快"需要的资源
  • 在方法返回之前抛出异常,触发"problem"
  • 资源没有被终结器及时释放,由于某种原因,终结器从未运行,但你的应用程序没有下降

所以,不,这并不重要。

如何修复?

public MyResponse MyMethod(string arg)
{
    MyResponse abc = null;
    try {
        abc = new MyResponse();
        using (Tracer myTracer = new Tracer(Constants.TraceLog))
        {
            // Some code
           return abc;
        }
    }
    catch {
        if (abc != null) {
            abc.Dispose();
        }
        throw;
    }
}

这确保了如果控制通过异常退出方法,abc要么是null,要么已被正确处置。

更新

事实证明,当使用这种方式处理事情时,从MyMethod 内部显式抛出的异常将被重新抛出,并将第一个堆栈帧的行号改变为指向throw;语句。

实际上,这意味着如果在MyResponse中有多个throw语句,并且它们抛出具有相同消息的相同类型的异常,那么当您捕获异常时,您将无法准确地告诉哪个throw负责。

在我看来这是一个纯粹的学术问题,但我提到它是为了完整。

这并不重要。但是,与@Aliostad相反,我认为版本2,在using块之外的return是更好的风格。

我的理由是这样的:

using块表示"打开"answers"关闭"的东西。这是一种廉价的交易。关闭using块表示我们已经完成了我们的工作,现在可以继续处理其他东西了,比如return

此警告可能与"单出口点"原则有关。这里讨论:http://c2.com/cgi/wiki?SingleFunctionExitPoint