Да, 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
определениям.
Однако в конкретном случае, о котором вы спрашиваете, на самом деле существует гораздо лучшая идиома. В общем, когда вы работаете с каким-то ресурсом, который необходимо очистить в конце, вы делаете это, передавая блок методу, который выполняет всю очистку за вас. Это похоже на блок using
в C #, за исключением того, что Ruby на самом деле достаточно мощный, чтобы вам не приходилось ждать, когда первосвященники Microsoft спустятся с горы и любезно поменяют свой компилятор для вас. В 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
. Но это общий шаблон, который вы также можете использовать в своем собственном коде для реализации любого вида очистки ресурсов (например, using
в C #) или транзакций или чего-либо еще, о чем вы могли подумать.
Единственный случай, когда это не работает, если получение и освобождение ресурса распределены по разным частям программы. Но если он локализован, как в вашем примере, вы можете легко использовать эти блоки ресурсов.
Кстати: в современном 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);
});