在使用{}语句块时调用return是一个好的做法吗?

117
我想知道在using块内部调用return是否安全/正确的方法。

例如:

using(var scope = new TransactionScope())
{
  // my core logic
  return true; // if condition met else
  return false;
  scope.Complete();
}

我们知道,在最后的右花括号dispose()会被调用。但在上述情况下会发生什么,因为return会将控制权跳出给定的范围(据我所知)...

  1. 我的scope.Complete()会被调用吗?
  2. 那么对于范围的dispose()方法呢。

1
一旦 using{} 作用域结束,相关对象将被处理,return 将“中断”该作用域 - 因此对象将按预期被处理。 - Shai
4
请注意,根据您提供的示例,scope.Complete() 方法永远不会被执行,因此您的事务将始终回滚。 - Andy
无论是否调用usingdispose(),当您返回时,包含此using块的函数将已经返回,并且属于它的所有内容都将被孤立。因此,即使scope没有被“使用”处理(正如其他人所解释的那样),它也将被处理,因为函数已经结束了。如果C#有goto语句——你笑完了吗?好的——那么你可以在不返回的情况下goto到闭合括号后面。从逻辑上讲,scope仍然会被处理,但是既然你已经在C#中放置了goto,那么在这个阶段谁还关心逻辑呢。 - Superbest
C# 有goto语句 - Jake T.
相关:在using块中间返回 - Palec
6个回答

165

在您的using块内调用return是完全安全的,因为using块只是一个try/finally块。

在您上面的示例中,如果调用return true,则作用域将被处理并返回该值。return falsescope.Complete()不会被调用。Dispose将始终被调用,因为它位于finally块内。

您的代码本质上与以下代码相同(如果更容易理解):

var scope = new TransactionScope())
try
{
  // my core logic
  return true; // if condition met else
  return false;
  scope.Complete();
}
finally
{
  if( scope != null) 
    ((IDisposable)scope).Dispose();
}
请注意,由于无法执行 scope.Complete() 来提交事务,因此您的事务将永远不会被提交。

15
你应该明确指出Dispose方法 将会 被调用。如果提问者不知道在using中会发生什么,那么他很可能也不知道在finally中会发生什么。 - Konrad Rudolph
离开using块并返回是可以的,但在TransactionScope的情况下,您可能会遇到using语句本身的问题: http://blogs.msdn.com/b/florinlazar/archive/2008/05/05/8459994.aspx - thewhiteambit
根据我的经验,这种情况在SQL Server CLR程序集中不起作用。当我需要返回一个包含引用MemoryStream对象的SqlXml字段的UDF的结果时,会出现“无法访问已释放的对象”和“尝试在流关闭时调用Read的无效尝试”等错误,因此我被迫编写泄漏代码,在这种情况下放弃使用语句。我的唯一希望是SQL CLR将处理这些对象的处理。这是一个独特的情况,但我想分享一下。 - MikeTeeVee
1
@MikeTeeVee - 更干净的解决方案要么是(a)让调用者执行using,例如 using (var callersVar = MyFunc(..)) ..而不是在"MyFunc"内部使用 - 我的意思是调用者获得流并负责通过using或显式关闭它,要么是(b)让MyFunc 提取所需的任何信息到其他对象中,可以安全地传递回来 - 然后底层数据对象或流可以由您的using处置。您不应该编写泄漏代码。 - ToolmakerSteve

7

没问题 - finally 子句(using 语句的结束大括号实际上就是 finally 子句)总是在离开作用域时执行,不管怎么样。

然而,这仅适用于位于 finally 块中的语句(使用 using 时无法显式设置)。因此,在你的示例中,scope.Complete() 永远不会被调用(我希望编译器会警告你有无法访问的代码)。


2

总体而言,这是一种不错的方法。但在您的情况下,如果在调用scope.Complete()之前返回,它将只是废弃TransactionScope。这取决于您的设计。

因此,在此示例中,未调用Complete(),并且假定已处理范围继承IDisposable接口。


它必须实现IDisposable接口,否则使用using将无法编译。 - Chriseyre2000

2

return之前,应该明确调用scope.Complete。编译器会显示警告,并且这段代码将永远不会被调用。

关于return本身-是安全的在using语句中调用它。使用背后被翻译为try-finally块,finally块一定会被执行。


1
在您提供的示例中存在一个问题;scope.Complete()从未被调用。 其次,在using语句内部使用return语句不是一个好习惯。请参考以下内容:
using(var scope = new TransactionScope())
{
    //have some logic here
    return scope;      
}

在这个简单的例子中,重点是:当使用语句结束时,scope的值将为null。
因此最好不要在using语句中返回。

1
仅仅因为“返回作用域”是无意义的,并不意味着返回语句是错误的。 - Preet Sangha
仅仅因为没有采用最佳实践并不意味着你做错了什么。这只是意味着最好避免使用,因为它可能会导致意想不到的后果。 - daryal
1
scope的值不会为null - 唯一发生的事情是该实例已被调用Dispose(),因此该实例不应再使用(但它不是null,并且没有任何阻止您尝试使用已处理的对象,即使这确实是可处理对象的不适当使用)。 - Lucero
Lucero说得很对。一次性对象在被处理后不是null。它的IsDisposed属性为true,但如果你检查是否为null,你会得到false,并且return scope返回对_该_对象的引用。这样,如果你在返回时分配了该引用,你就可以防止GC清理已处理的对象。 - ThunderGr

0
在这个例子中,scope.Complete()永远不会执行。然而,return命令将清理分配在堆栈上的所有内容。GC将处理所有未引用的内容。因此,除非存在无法被GC捕获的对象,否则没有问题。

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