提问



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


我想知道ensure是否是C#中Ruby 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,即使没有引发异常?

最佳参考


是的,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。你也可以省略变量,在这种情况下,你不能在异常处理代码中检查异常。(好吧,你总是可以使用全局异常变量来访问引发的最后一个异常,但是那个sa有点hacky。)你可以省略异常类,在这种情况下,将捕获从StandardError继承的所有异常。 (请注意,这并不意味着会捕获所有异常,因为有Exception但不是StandardError的实例。大多数非常严重的异常会损害完整性的程序如SystemStackErrorNoMemoryErrorSecurityErrorNotImplementedErrorLoadErrorSyntaxErrorScriptErrorInterruptSignalExceptionSystemExit。)


一些块形成隐式异常块。例如,方法定义也隐含地也是异常块,因此不是写入


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或事务或您可能想到的任何其他内容。


如果获取和释放资源不分散在程序的不同部分,这是唯一不起作用的情况。但如果它是本地化的,如在您的示例中,那么您可以轻松使用这些资源块。





BTW:在现代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);
});

其它参考1


FYI,即使在rescue部分中重新引发异常,ensure块也将在代码执行继续到下一个异常处理程序之前执行。例如:


begin
  raise "Error!!"
rescue
  puts "test1"
  raise # Reraise exception
ensure
  puts "Ensure block"
end

其它参考2


如果要确保文件已关闭,则应使用File.open的块形式:


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

其它参考3


是的,在任何情况下都会调用ensure。有关更多信息,请参阅编程Ruby书籍的异常,捕获和抛出并搜索确保。[57]

其它参考4


是的,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语句。

其它参考5


这就是我们需要ensure的原因:


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

其它参考6


是的,ensurefinally 保证将执行该块。这对于确保关键资源受到保护非常有用,例如:在出错时关闭文件句柄或释放互斥锁。