使用块从内部返回是不是可以

Posted

技术标签:

【中文标题】使用块从内部返回是不是可以【英文标题】:Is it OK doing a return from inside using block使用块从内部返回是否可以 【发布时间】:2011-07-30 21:30:27 【问题描述】:

我正在做代码审查,发现很多代码格式如下:

public MyResponse MyMethod(string arg)

    using (Tracer myTracer = new Tracer(Constants.TraceLog))
    
        MyResponse abc = new MyResponse();

        // Some code

        return abc;
    

当我运行代码分析时,我收到 CA2000 警告 Microsoft.Reliability

代码是否应该改写为:

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.Reliability:在方法“xxxxx(Guid, Guid)”中,对象“MyResponse”并未沿所有异常路径进行处理。在对对象“MyResponse”的所有引用超出范围之前调用 System.IDisposable.Dispose。

【问题讨论】:

MyResponse IDisposable? 这个警告没有任何意义恕我直言......我看不出第二个版本会更好的任何理由 @Jon,不管是不是,MyResponse 不受using 的处置语义的约束。这不是这里的问题,除了返回的对象。 您是否出于某种原因将警告文本保密?我们没有精神力量;如果您想诊断错误消息发布消息 @Eric:通常情况下,我会同意你的看法,但 CA2000 和他的例子很清楚,你不这么认为吗? 【参考方案1】:

不,没关系。

using 语句隐式生成的用于处理处置的finally 块将执行,无论您将return 放在何处。

您确定 CA2000 与 myTracer 而不是 abc 相关吗?我猜想警告正在发生,因为MyResponse 实现了IDisposable,并且您在返回之前没有处理abc。 (无论哪种方式,您建议的重写应该对警告没有影响。)

【讨论】:

@smartcaveman - 问题以“或者这无关紧要?”的问题结束。所以这个答案开头的陈述非常好。 @Richard,我没说错。我说这在标题的上下文中令人困惑。 我不想挑剔,但我不明白为什么这个答案得到了这么多的支持。第一部分与提供的警告无关,第二部分没有提供解决方案... @Daniel:OP 询问“是否可以从 using 块内部返回”和“是否应该重写代码以将返回移动到 using 块之外”。我正在按照要求回答这些问题,然后推测问题的真正根本原因(推测是因为 OP 没有提供足够的具体细节)。您自己的答案为(假定的)实际问题提供了解决方案,如果您认为值得,我很乐意在这里重现类似的内容。【参考方案2】:

您的重写不会修复 CA2000 警告,因为问题不是 Tracer 对象,而是 MyResponse 对象。The documentation 声明:

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

要修复警告without messing with the stack trace of your exceptions

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;

为什么?请参阅链接文档中的示例。

【讨论】:

我认为try/catch 可能更可取,因为它不需要您创建临时变量——这里的治疗方法可能比疾病更糟糕。 这完全取决于你的情况,如果你想实现 something 来修复这个警告。如果不经常调用该方法,我不会因为我不认为这是一个问题。但是,我不同意使用try/catch,因为它可能会混淆异常的堆栈跟踪(请参阅答案中的链接)。 顺便说一句:我提供的修复是MS官方建议修复此警告。 @DanielHilgarth:编程中没有“力量”; throw; 不会弄乱堆栈跟踪,仅此而已。至于官方的建议,我认为作为专业人士,我们不应该盲目“听从命令”。我们可以并且应该独立判断每种方法的优点。无论如何,我并不是在这里提倡一种真正的解决方案。只是说如果可读性对你和我一样重要,try/catch 可能会更好。 @Jon: 阅读提供的链接,如果异常以与 try/catch 相同的方法抛出,则 确实 弄乱堆栈跟踪!关于盲目听从命令:我完全同意,但提供的解决方案是解决问题没有副作用的方式。【参考方案3】:

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

为什么会出现警告?

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

重要吗?

一般来说,只有在以下情况下才重要:

IDisposable 封装了程序的其他部分或其他进程可能“很快”需要的资源 方法返回前抛出异常,触发“问题” 终结器没有尽快释放该资源,由于某种原因,终结器永远不会运行,但您的应用程序不会关闭

所以不,这应该不重要。

如何解决?

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 内部显式抛出的异常将被重新抛出,并且第一个堆栈帧的行号会更改为指向 @987654331 @ 声明。

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

恕我直言,这是一个纯粹的学术问题,但为了完整起见,我提到它。

【讨论】:

我总是会处理 IDisposable 对象。不能保证 IDisposable 类甚至有终结器。 @TrueWill:所以你会在你的代码中禁止任何返回IDisposable的方法?那是不是有点过分了?这是该策略的一个受害者:msdn.microsoft.com/en-us/library/b9skfh7s.aspx。一次性物品应该被丢弃,当然,但只有在我们用完之后 我不会禁止这样做。方法可以返回一个 IDisposable,但每个一次性实例都必须有一个所有者(从某种意义上说是负责处置它的东西)。方法契约(可能是 XML cmets)应该指定调用者是否负责处理返回的实例。 顺便说一句:您可以完全跳过 abc=null 分配和 if(abc!=null) 检查,只需将 abc = new MyResponse(); 放在 try 块之前 - 无需任何更改(既不会丢失异常也不会丢弃实例)。 【参考方案4】:

这并不重要。但是,与@Aliostad 不同,我认为在using 块之外使用return 的版本2 更好。

我的理由是这样的:

using 块表示“打开”和“关闭”的东西。这是一种廉价交易。关闭using 块表示我们已经完成了我们的工作,现在可以安全地继续处理其他内容,例如returning。

【讨论】:

【参考方案5】:

这个警告很可能与“单一出口点”的原则有关。这里讨论:http://c2.com/cgi/wiki?SingleFunctionExitPoint

【讨论】:

以上是关于使用块从内部返回是不是可以的主要内容,如果未能解决你的问题,请参考以下文章

为啥代理下的getContextPath()返回的是HttpServlet内部的内部路径,而不是Filter内部的路径?

Lua 函数如何返回 nil,即使函数内部的返回值不是 nil?

在windows系统下的cmd下面执行php 命令,返回‘PHP’不是内部或外部命令,也不是可运行的程序。。

Magento 而不是显示我的块热门标签块从默认主题显示

adb shell 命令返回'grep' 不是内部或外部命令,也不是可运行的程序

是否可以在函数内部分配数组并使用引用返回它?