Eine using Anweisung statt eines try-finally-Dispose Konstrukts zu verwenden erhöht die Lesbarkeit des Quellcodes. Ausserdem wird verhindert das Aufräumarbeiten für benutzte Ressourcen vergessen werden. Die using Anweisung hat sich daher gut durchgesetzt und wird von vielen Entwicklern genutzt. Zu einem Punkt scheint es aber Unklarheiten zu geben. Dies schliesse ich daraus, dass mir die folgende Frage schon des Öfteren gestellt wurde: Was geschieht wenn im Constructor, welcher in der using Anweisung aufgerufen wird, eine Exception geworfen wird?
In diesem Artikel möchte ich die Fragestellung anhand eines Beispiels beantworten. Der nachfolgende Quellcode zeigt wie ein FileStream geöffnet und aus diesem ein Byte gelesen wird. Dabei wird die using Anweisung genutzt.
1. Using Anweisung zum Zugriff auf einen FileStream
1 using(FileStream stream = new FileStream(@“c:\not_existing.txt“, FileMode.Open))
2 {
3 stream.ReadByte();
4 }
Wenn dieser Code ausgeführt wird, dann wird durch den Constructor der FileStream Klasse eine FileNotFoundException geworfen (ausser sie legen die Datei zuvor an). Wie kann diese Exception gefangen werden?
Wie Sie wissen ist die using Anweisung nur syntaktischer Zuckerguss für ein try-finally-Dispose Konstrukt. Betrachten wir daher zunächst wie die Anweisung umgewandelt wird. Der nachfolgende Quellcode zeigt das entsprechende try-finally-Dispose Konstrukt.
2. Using umgewandelt in try-finally-Dispose Konstrukt
1 FileStream stream = new FileStream(@“c:\not_existing.txt“, FileMode.Open);
2 try
3 {
4 stream.ReadByte();
5 }
6 finally
7 {
8 if (stream != null)
9 {
10 stream.Dispose();
11 }
12 }
Kommen wir nun zurück zur ursprünglichen using Anweisung aus Beispielcode 1. Um die FileNotFoundException abzufangen kann die using Anweisung um einen try-catch Block ergänzt werden. Muss dieser Block nun aber intern in die Anweisung aufgenommen werden oder diese extern umschliessen? Nachfolgend sehen Sie beide Varianten.
3. Using ergänzt um internen und externen try-catch Block
3a) Interner try-catch Block
1 using (FileStream stream = new FileStream(@“c:\not_existing.txt“, FileMode.Open))
2 {
3 try
4 {
5 stream.ReadByte();
6 }
7 catch (FileNotFoundException)
8 {
9 //error handling
10 }
11 }
3b) Externer try-catch Block
1 try
2 {
3 using (FileStream stream = new FileStream(@“c:\not_existing.txt“, FileMode.Open))
4 {
5 stream.ReadByte();
6 }
7 }
8 catch (FileNotFoundException)
9 {
10 //error handling
11 }
In welchem Fall wird Ihrer Meinung nach die Exception abgefangen? Nur im Fall 3b. Der interne try-catch Block aus Beispielcode 3a wird nicht ausgeführt, da die Exception bereits vorher im Constructor in der using Anweisung geworfen wird. Nur der externe try-catch Block kann daher zur Fehlerbehandlung einer Constructor Exception genutzt werden.
Mir persönlich gefällt dieser Quellcode aber nicht. Ich empfinde diese Lösung als umständlich und schlecht lesbar. Daher möchte ich Ihnen zum Vergleich eine weitere Lösung anbieten. Statt die using Anweisung zu nutzen, habe ich im nachfolgenden Beispiel einen klassischen try-catch-finally Block eingesetzt.
4. Try-catch-finally statt Using
1 FileStream stream = null;
2
3 try
4 {
5 stream = new FileStream(@“c:\not_existing.txt“, FileMode.Open);
6 stream.ReadByte();
7 }
8 catch (FileNotFoundException)
9 {
10 //error handling
11 }
12 finally
13 {
14 if (stream != null)
15 {
16 stream.Dispose();
17 }
18 }
Der Quellcode hat die gleiche Funktionalität wie der Quellcode in 3b. Ich empfinde diese Lösung aber als wesentlich besser lesbar.
Fazit
Die using Anweisung ist einem try-finally-Dispose Konstrukt im Allgemeinen vorzuziehen. Wenn im Constructor innerhalb des usings aber eine Exception geworfen werden könnte und diese direkt behandelt werden soll, dann ist ein klassischer try-catch-finally Block vorzuziehen. Dadurch wird die Lesbarkeit des Quellcodes verbessert.
Was ist mit der Möglichkeit:
FileStream stream = null;
try
{
stream = new FileStream(@”c:\not_existing.txt”, FileMode.Open);
}
catch (FileNotFoundException)
{
//error handling
return;
}
using(FileStream stream)
{
stream.ReadByte();
}
Dies ist ebenfalls eine mögliche Lösung. Hierbei muss man aber beachten dass das ReadByte ebenfalls Fehler werfen könnte. Somit würde man ein zusätzlichen try-catch Block benötigen. Durch Vereinfachung würde man dann wieder zu einem ähnlichen Code wie in 3b gezeigt kommen.