Ruby中的开始、Rescue和Ensure?

x6yk4ghg  于 2023-08-04  发布在  Ruby
关注(0)|答案(7)|浏览(174)

我最近开始用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是否无论如何都会被调用,即使没有引发异常?

gfttwv5a

gfttwv5a1#

是的,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。您也可以省略变数,在这种情况下,您将无法在异常处理程式码中检查异常。(当然,您可以始终使用全局异常变量来访问最后一个引发的异常,但这有点麻烦。)您可以省略exception类,在这种情况下,将捕获所有继承自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)或事务或您可能想到的任何其他内容。
如果获取和释放资源分布在程序的不同部分,则这是唯一不起作用的情况。但是如果它是本地化的,就像在您的示例中一样,那么您可以很容易地使用这些资源块。
顺便说一句:在现代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);
});

quhf5bfb

quhf5bfb2#

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

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

字符串

ljsrvy3e

ljsrvy3e3#

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

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

字符串

xwbd5t1u

xwbd5t1u4#

这就是为什么我们需要ensure

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

字符串

n3ipq98p

n3ipq98p5#

是的,ensure在任何情况下都会被调用。有关更多信息,请参阅《Programming Ruby》一书中的“Exceptions, Catch, and Throw”,并搜索“ensure”。

zzzyeukh

zzzyeukh6#

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

wz3gfoph

wz3gfoph7#

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

相关问题