开始,救援和确保在Ruby?

我最近开始用Ruby编程,我正在研究异常处理。

我想知道ensure在Ruby中是否相当于c#中的finally ?我应该:

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也会被调用吗?

477163 次浏览

是的,在任何情况下都会调用ensure。有关更多信息,请参阅Programming Ruby书中的“异常、Catch和Throw”,并搜索“ensure”。

如果你想要确保一个文件是关闭的,你应该使用File.open块形式:

File.open("myFile.txt", "w") do |file|
begin
file << "#{content} \n"
rescue
#handle the error here
end
end

是的,ensure就像finally 保证块将被执行。这对于确保关键资源受到保护非常有用,例如在错误时关闭文件句柄,或释放互斥量。

是的,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确保代码总是被求值。这就是为什么它被称为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

你可以省略rescueensureelse。您还可以省略变量,在这种情况下,您将无法在异常处理代码中检查异常。(好吧,你总是可以使用全局异常变量来访问引发的最后一个异常,但这有点笨拙。)并且可以省略异常类,在这种情况下,继承自StandardError的所有异常都将被捕获。(请注意,这并不意味着ensure6异常被捕获,因为有些异常是Exception的实例,而不是StandardError的实例。大多数是非常严重的异常,损害程序的完整性,如SystemStackErrorNoMemoryErrorSecurityErrorNotImplementedErrorensure0, ensure1, ensure2, ensure3, ensure4或ensure5。)

一些块形成隐式异常块。例如,方法定义也是隐式异常块,所以不需要编写

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#中的à la 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

这就是为什么我们需要ensure:

def hoge
begin
raise
rescue
raise # raise again
ensure
puts 'ensure' # will be executed
end
puts 'end of func' # never be executed
end