我最近开始使用 Ruby 进行编程,并且正在研究异常处理。
我想知道 ensure
是否是 C# 中 finally
的 Ruby 等价物?我应该有:
file = File.open("myFile.txt", "w")
begin
file << "#{content} \n"
rescue
#handle the error here
ensure
file.close unless file.nil?
end
还是我应该这样做?
#store the file
file = File.open("myFile.txt", "w")
begin
file << "#{content} \n"
file.close
rescue
#handle the error here
ensure
file.close unless file.nil?
end
无论如何都会调用 ensure
,即使没有引发异常?
begin
块内。
是的,ensure
确保始终评估代码。这就是它被称为 ensure
的原因。因此,它相当于 Java 和 C# 的 finally
。
begin
/rescue
/else
/ensure
/end
的一般流程如下所示:
begin
# something which might raise an exception
rescue SomeExceptionClass => some_variable
# code that deals with some exception
rescue SomeOtherException => some_other_variable
# code that deals with some other exception
else
# code that runs only if *no* exception was raised
ensure
# ensure that this code always runs, no matter what
# does not change the final value of the block
end
您可以省略 rescue
、ensure
或 else
。您还可以省略变量,在这种情况下您将无法在异常处理代码中检查异常。 (好吧,您总是可以使用全局异常变量来访问引发的最后一个异常,但这有点笨拙。)您可以省略异常类,在这种情况下,从 StandardError
继承的所有异常都将是抓住。 (请注意,这并不意味着所有异常都被捕获,因为有些异常是 Exception
而不是 StandardError
的实例。大多数非常严重的异常会损害程序的完整性,例如如 SystemStackError
、NoMemoryError
、SecurityError
、NotImplementedError
、LoadError
、SyntaxError
、ScriptError
、Interrupt
、SignalException
或 SystemExit
。)
一些块形成隐式异常块。例如,方法定义也是隐式的异常块,所以不要写
def foo
begin
# ...
rescue
# ...
end
end
你只写
def foo
# ...
rescue
# ...
end
或者
def foo
# ...
ensure
# ...
end
这同样适用于 class
定义和 module
定义。
但是,在您询问的特定情况下,实际上有一个更好的习语。通常,当您使用一些最终需要清理的资源时,您可以通过将块传递给为您完成所有清理的方法来实现。它类似于 C# 中的 using
块,除了 Ruby 实际上足够强大,您不必等待微软的大祭司从山上下来并为您慷慨地更改他们的编译器。在 Ruby 中,您可以自己实现它:
# This is what you want to do:
File.open('myFile.txt', 'w') do |file|
file.puts content
end
# And this is how you might implement it:
def File.open(filename, mode='r', perm=nil, opt=nil)
yield filehandle = new(filename, mode, perm, opt)
ensure
filehandle&.close
end
您知道吗:这已经在核心库中作为 File.open
提供。但它是一种通用模式,您也可以在自己的代码中使用,以实现任何类型的资源清理(在 C# 中的 using
)或事务或您可能想到的任何其他内容。
如果获取和释放资源分布在程序的不同部分,那么这不起作用的唯一情况。但是,如果它是本地化的,如您的示例中那样,那么您可以轻松地使用这些资源块。
顺便说一句:在现代 C# 中,using
实际上是多余的,因为您可以自己实现 Ruby 样式的资源块:
class File
{
static T open<T>(string filename, string mode, Func<File, T> block)
{
var handle = new File(filename, mode);
try
{
return block(handle);
}
finally
{
handle.Dispose();
}
}
}
// Usage:
File.open("myFile.txt", "w", (file) =>
{
file.WriteLine(contents);
});
仅供参考,即使在 rescue
部分重新引发异常,ensure
块也会在代码继续执行到下一个异常处理程序之前执行。例如:
begin
raise "Error!!"
rescue
puts "test1"
raise # Reraise exception
ensure
puts "Ensure block"
end
如果要确保文件已关闭,则应使用 File.open
的块形式:
File.open("myFile.txt", "w") do |file|
begin
file << "#{content} \n"
rescue
#handle the error here
end
end
这就是我们需要 ensure
的原因:
def hoge
begin
raise
rescue
raise # raise again
ensure
puts 'ensure' # will be executed
end
puts 'end of func' # never be executed
end
是的,ensure
确保它每次都运行,因此您不需要 begin
块中的 file.close
。
顺便说一句,一个很好的测试方法是:
begin
# Raise an error here
raise "Error!!"
rescue
#handle the error here
ensure
p "=========inside ensure block"
end
可以测试一下,出现异常时是否会打印出“=========inside ensure block”。然后,您可以注释掉引发错误的语句,并通过查看是否打印出任何内容来查看是否执行了 ensure
语句。
是的,ensure
像 finally
保证该块将被执行。这对于确保关键资源受到保护非常有用,例如在出错时关闭文件句柄或释放互斥锁。
File.open
部分不在 begin-ensure 块内。只有 file.close
是,但这还不够。
不定期副业成功案例分享
ensure
语句最后执行,但它们不是返回值。ensure
。”using
的需求。open
方法仍然需要进行清理。该示例只是以冗长(而不是 100% 防弹)的方式执行此操作,而不是使用using
速记。我建议尽可能用using
代替try-finally
。