在使用块中间返回

225

类似于:

using (IDisposable disposable = GetSomeDisposable())
{
    //.....
    //......
    return Stg();
}

我认为在这里使用return语句不太合适,对吗?

7个回答

220

正如其他人普遍指出的那样,在一般情况下,这不是一个问题。

唯一会导致问题的情况是,如果您在 using 语句中间返回并且同时返回 using 变量。但是,即使您不返回并仅保留变量的引用,这也会导致问题。

using ( var x = new Something() ) { 
  // not a good idea
  return x;
}

同样糟糕

Something y;
using ( var x = new Something() ) {
  y = x;
}

1
我正要编辑我的问题,关于你提到的那个点。谢谢。 - tafa
请帮助我理解为什么这是不好的。我想要将在帮助函数中使用的 Stream 传递给另一个用于图像处理的函数。但是,如果我这样做,Stream 是否会被释放? - John Shedletsky
4
在这种情况下,你的函数调用应该被包装在 using 语句块中。例如:using(Stream x = FuncToReturnStream()){...} ,而且 FuncToReturnStream 函数内部不使用 using 语句。 - Felix Keil
@JohnShedletsky 我相信这是因为 return 语句使得 using 块的结尾对于任何代码路径都不可访问。需要运行 using 块的结尾以便在需要时处理对象的释放。 - mekb

170

没问题。

你似乎在想

using (IDisposable disposable = GetSomeDisposable())
{
    //.....
    //......
    return Stg();
}

被盲目翻译成:

IDisposable disposable = GetSomeDisposable()
//.....
//......
return Stg();
disposable.Dispose();

不可否认,这会是一个问题,并且会使 using 语句变得毫无意义 --- 这就是为什么它不会这样做的原因

编译器确保在控制离开块之前清除对象 - 无论控制如何离开块。


很棒的回答@James Curran!但是这让我非常好奇它被翻译成了什么。或者只能用IL表达吗?(我以前从未尝试过阅读)。 - Bart
1
@Bart - 我认为它将返回表达式评估为临时变量,然后进行处理,最后返回临时变量。 - ToolmakerSteve
@James Curran。从一开始到现在,只有你解释了背后发生的事情。非常感谢。 - Sercan Timoçin
1
@Bart 可能已经翻译成:try { ...你的代码... } finally { x.Dispose(); } - Bip901
相关的是,我发现了一个很好的解决方案,用于处理在函数返回后需要“使用”的IDisposable对象,那就是传入一个Func。例如,我有一些辅助方法用于创建连接和执行查询,但是我可以传入一个Func<IDataReader, T>来提取结果。我的代码只需要一个using块,而我的其他函数则专注于设置查询和参数。 - Adam

106

完全没问题,为什么您认为它有错呢?

using语句只是try/finally块的语法糖,正如Grzenio所说,从try块中返回也是可以的。

return表达式将被评估,然后执行finally块,最后方法将返回。


5
James Curran的回答解释了我所想的。 - tafa

30

这样做完全没有问题,就像在 try{}finally{} 中途返回一样。


20

这是完全可以接受的。使用 using 语句可以确保 IDisposable 对象无论如何都将被处理。

来自MSDN

using 语句确保即使在调用对象方法时发生异常,也会调用 Dispose 方法。您可以通过将对象放置在 try 块中,然后在 finally 块中调用 Dispose 来实现相同的结果;事实上,这就是编译器如何将 using 语句转换的方式。


19
下面的代码展示了如何使用 using
private class TestClass : IDisposable
{
   private readonly string id;

   public TestClass(string id)
   {
      Console.WriteLine("'{0}' is created.", id);
      this.id = id;
   }

   public void Dispose()
   {
      Console.WriteLine("'{0}' is disposed.", id);
   }

   public override string ToString()
   {
      return id;
   }
}

private static TestClass TestUsingClose()
{
   using (var t1 = new TestClass("t1"))
   {
      using (var t2 = new TestClass("t2"))
      {
         using (var t3 = new TestClass("t3"))
         {
            return new TestClass(String.Format("Created from {0}, {1}, {2}", t1, t2, t3));
         }
      }
   }
}

[TestMethod]
public void Test()
{
   Assert.AreEqual("Created from t1, t2, t3", TestUsingClose().ToString());
}

输出:

't1' 已创建。
't2' 已创建。
't3' 已创建。
已从 t1、t2、t3 创建 'Created from t1, t2, t3'。
't3' 已销毁。
't2' 已销毁。
't1' 已销毁。

已释放的内容在返回语句之后但在函数退出之前被调用。


1
请注意,一些C#对象以自定义方式进行处理,例如,WCF客户端使用类似上面的using语句返回“无法访问已释放的对象”。 - OzBob

-4
也许这并不完全正确,这是可以接受的...
如果您正在嵌套使用并从嵌套中返回,则可能不安全。
以此为例:
using (var memoryStream = new MemoryStream())
{
    using (var textwriter = new StreamWriter(memoryStream))
    {
        using (var csv = new CsvWriter(textwriter))
        {
            //..write some stuff to the stream using the CsvWriter
            return memoryStream.ToArray();
        }
    }
}

我正在传递一个 DataTable 以输出为 csv。在中间返回时,它会将所有行写入流中,但输出的 csv 总是缺少一行(或多行,取决于缓冲区的大小)。这告诉我某些东西没有被正确关闭。
正确的方法是确保所有先前的 using 均已正确处理:
using (var memoryStream = new MemoryStream())
{
    using (var textwriter = new StreamWriter(memoryStream))
    {
        using (var csv = new CsvWriter(textwriter))
        {
            //..write some stuff to the stream using the CsvWriter
        }
    }

    return memoryStream.ToArray();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接