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

我想知道Ruby中的ensure是否等同于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

确保得到调用无论什么,即使异常没有被引发?


当前回答

供参考,即使在救援部分重新引发异常,确保块也将在代码继续执行到下一个异常处理程序之前执行。例如:

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

其他回答

供参考,即使在救援部分重新引发异常,确保块也将在代码继续执行到下一个异常处理程序之前执行。例如:

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  

是的,确保确保它每次都运行,所以你不需要这个文件。在开始块中关闭。

顺便说一下,测试的好方法是:

begin
  # Raise an error here
  raise "Error!!"
rescue
  #handle the error here
ensure
  p "=========inside ensure block"
end

您可以测试一下,当出现异常时,是否会打印出“=========inside ensure block”。 然后,您可以注释掉引发错误的语句,并通过查看是否打印出任何内容来查看ensure语句是否被执行。

是的,ensure确保代码总是被求值。这就是为什么它被称为确保。因此,它最终等价于Java和c#。

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

You can leave out rescue, ensure or else. You can also leave out the variables in which case you won't be able to inspect the exception in your exception handling code. (Well, you can always use the global exception variable to access the last exception that was raised, but that's a little bit hacky.) And you can leave out the exception class, in which case all exceptions that inherit from StandardError will be caught. (Please note that this does not mean that all exceptions are caught, because there are exceptions which are instances of Exception but not StandardError. Mostly very severe exceptions that compromise the integrity of the program such as SystemStackError, NoMemoryError, SecurityError, NotImplementedError, LoadError, SyntaxError, ScriptError, Interrupt, SignalException or SystemExit.)

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

def foo
  begin
    # ...
  rescue
    # ...
  end
end

你只写

def foo
  # ...
rescue
  # ...
end

or

def foo
  # ...
ensure
  # ...
end

这同样适用于类定义和模块定义。

However, in the specific case you are asking about, there is actually a much better idiom. In general, when you work with some resource which you need to clean up at the end, you do that by passing a block to a method which does all the cleanup for you. It's similar to a using block in C#, except that Ruby is actually powerful enough that you don't have to wait for the high priests of Microsoft to come down from the mountain and graciously change their compiler for you. In Ruby, you can just implement it yourself:

# 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)或事务或任何您可能想到的东西。

如果获取和释放资源分布在程序的不同部分,那么这种方法是行不通的。但如果它是本地化的,就像在您的示例中一样,那么您可以轻松地使用这些资源块。


顺便说一句:在现代c#中,使用实际上是多余的,因为你可以自己实现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);
});