Ja, ensure
sorgt dafür, dass der Code immer ausgewertet wird. Deshalb heißt es ja auch ensure
. Es ist also gleichwertig mit dem von Java und C# finally
.
Der allgemeine Fluss der begin
/ rescue
/ else
/ ensure
/ end
sieht so aus:
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
Sie können weglassen rescue
, ensure
ou else
. Sie können die Variablen auch weglassen. In diesem Fall können Sie die Ausnahme in Ihrem Code zur Ausnahmebehandlung nicht überprüfen. (Nun, Sie können immer die globale Ausnahmevariable verwenden, um auf die zuletzt ausgelöste Ausnahme zuzugreifen, aber das ist ein bisschen umständlich.) Und Sie können die Ausnahmeklasse weglassen, in diesem Fall werden alle Ausnahmen, die von StandardError
gefangen werden. (Bitte beachten Sie, dass dies nicht bedeutet, dass alle Ausnahmen abgefangen werden, weil es Ausnahmen gibt, die Instanzen von Exception
aber nicht StandardError
. Meistens sehr schwerwiegende Ausnahmen, die die Integrität des Programms gefährden, wie z. B. SystemStackError
, NoMemoryError
, SecurityError
, NotImplementedError
, LoadError
, SyntaxError
, ScriptError
, Interrupt
, SignalException
ou SystemExit
.)
Einige Blöcke bilden implizite Ausnahmeblöcke. Zum Beispiel sind Methodendefinitionen implizit auch Ausnahmeblöcke, so dass man statt
def foo
begin
# ...
rescue
# ...
end
end
Sie schreiben nur
def foo
# ...
rescue
# ...
end
ou
def foo
# ...
ensure
# ...
end
Das Gleiche gilt für class
Definitionen und module
Definitionen.
In dem speziellen Fall, nach dem Sie fragen, gibt es jedoch ein viel besseres Idiom. Wenn Sie mit einer Ressource arbeiten, die Sie am Ende aufräumen müssen, tun Sie das im Allgemeinen, indem Sie einen Block an eine Methode übergeben, die alle Aufräumarbeiten für Sie erledigt. Das ist vergleichbar mit einer using
Block in C#, mit dem Unterschied, dass Ruby tatsächlich so leistungsfähig ist, dass man nicht darauf warten muss, dass die Hohepriester von Microsoft vom Berg herunterkommen und gnädigerweise ihren Compiler für einen ändern. In Ruby können Sie es einfach selbst implementieren:
# 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
Und wer hätte das gedacht: Das ist bereits verfügbar in der Kernbibliothek als File.open
. Aber es ist ein allgemeines Muster, das Sie auch in Ihrem eigenen Code verwenden können, um jede Art von Ressourcenbereinigung zu implementieren (à la using
in C#) oder Transaktionen oder was Ihnen sonst noch einfällt.
Der einzige Fall, in dem dies nicht funktioniert, ist, wenn die Beschaffung und Freigabe der Ressource über verschiedene Teile des Programms verteilt sind. Wenn sie jedoch lokalisiert ist, wie in Ihrem Beispiel, dann können Sie diese Ressourcenblöcke problemlos verwenden.
Übrigens: In modernem C#, using
ist eigentlich überflüssig, da Sie Ressourcenblöcke im Stil von Ruby selbst implementieren können:
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);
});
2 Stimmen
Beides ist nicht gut. Wenn Sie mit externen Ressourcen arbeiten, sollten Sie in der Regel immer wollen, dass die Ressource "opening" innerhalb der
begin
Block.