Tak, ensurezapewnia, że kod jest zawsze analizowany. Dlatego się nazywa ensure. Jest to więc odpowiednik Javy i C # finally.
Ogólny przepływ begin/ rescue/ else/ ensure/ endwygląda następująco:
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
Można pominąć rescue, ensurelub else. Możesz także pominąć zmienne, w którym to przypadku nie będziesz mógł sprawdzić wyjątku w kodzie obsługi wyjątków. (Cóż, zawsze możesz użyć globalnej zmiennej wyjątku, aby uzyskać dostęp do ostatniego zgłoszonego wyjątku, ale to jest trochę zhackowane.) I możesz pominąć klasę wyjątków, w którym to przypadku StandardErrorwychwycone zostaną wszystkie wyjątki, które dziedziczą . (Należy pamiętać, że to nie oznacza, że wszystkie wyjątki zostały złowione, ponieważ istnieją wyjątki, które są przypadki Exception, ale nie StandardError. Głównie bardzo poważne wyjątki że kompromis integralność programu, takie jak SystemStackError, NoMemoryError, SecurityError, NotImplementedError, LoadError, SyntaxError, ScriptError, lubInterrupt ,SignalExceptionSystemExit.)
Niektóre bloki tworzą niejawne bloki wyjątków. Na przykład definicje metod są domyślnie także blokami wyjątków, więc zamiast pisania
def foo
begin
# ...
rescue
# ...
end
end
piszesz tylko
def foo
# ...
rescue
# ...
end
lub
def foo
# ...
ensure
# ...
end
To samo dotyczy classdefinicji i moduledefinicji.
Jednak w konkretnym przypadku, o który pytasz, istnieje o wiele lepszy idiom. Ogólnie rzecz biorąc, gdy pracujesz z jakimś zasobem, który musisz oczyścić na końcu, robisz to, przekazując blok metodzie, która wykonuje za Ciebie całe czyszczenie. Jest podobny do usingbloku w języku C #, z tym wyjątkiem, że Ruby jest wystarczająco potężny, abyś nie musiał czekać na najwyższych kapłanów Microsoftu, którzy zejdą z góry i łaskawie zmienią dla ciebie kompilator. W Ruby możesz to po prostu zaimplementować:
# 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
A co wiesz: jest to już dostępne w podstawowej bibliotece jako File.open. Ale jest to ogólny wzorzec, którego można również użyć we własnym kodzie, do implementacji wszelkiego rodzaju czyszczenia zasobów (à la usingw języku C #) lub transakcji lub cokolwiek innego, co możesz wymyślić.
Jedyny przypadek, w którym to nie działa, jeśli pozyskiwanie i zwalnianie zasobów jest rozłożone na różne części programu. Ale jeśli jest zlokalizowane, jak w twoim przykładzie, możesz łatwo użyć tych bloków zasobów.
BTW: we współczesnym języku C # usingjest w rzeczywistości zbędny, ponieważ możesz samodzielnie implementować bloki zasobów w stylu 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);
});
beginbloku.