Advanced   Java   Services close, dispose und using Back Next Up Home


Close, Dispose und using

Man öffnet eine Datei (zum Lesen und/oder Schreiben), man bearbeitet sie, und am Ende schließt man sie. An diesen drei Schritten hat sich seit ihrer Einführung nichts mehr geändert. Bei jedem der drei Schritte können Fehler auftreten, die man mit einem try-catch-Konstrukt abfangen kann. Es empfiehlt sich hierzu über Exceptions Bescheid zu wissen. In C# gibt es darüber hinaus das using-Konstrukt, welches sich als eine Kurzform eines spezielles try-catch-finally-Konstrukts herausstellen wird. Wir beginnen mit dem klassischen try-catch Konstrukt.


Dateihandling mit try-catch

Wir wählen den StreamWriter. Mit ihm kann man etwa Text in eine Datei fast genauso einfach schreiben wie auf die Console. Ohne Fehlerbehandlung sähe das so aus.

using System;
using System.IO;

namespace io
{
   class StreamWriterDemo
   {
      static void Main()
      {
         StreamWriter sw = new StreamWriter("test.txt"); // Öffnen
            // UnauthorizedAccessException
            // ArgumentException
            // ArgumentNullException
            // DirectoryNotFoundException
            // PathTooLongException
            // IOException
            // SecurityException

         sw.Write("foo");  // Bearbeiten
            // ObjectDisposedException
            // NotSupportedException
            // IOException

         sw.Close();  // Schließen
            // EncoderFallbackException
      }
   }
}

Hier ist natürlich eine Fehlerbehandlung Pflicht. Die möglichen Exceptions sind bereits als Kommentar aufgelistet

using System;
using System.IO;

namespace io
{
   class StreamWriterDemo
   {
      static void Main()
      {
         StreamWriter sw = null;
         try
         {
            sw = new StreamWriter("test.txt"); // Öffnen
            sw.Write("foo");  // Bearbeiten
         }
         catch(Exception ex)
         {
            Console.WriteLine(ex.Message);
         }
         finally
         {
            try
            {
               if(sw!=null) sw.Close();  // Schließen
            }
            catch(Exception ex)
            {
            }
         }
      } // end Main
   }
}

sw.Close() muß im finally-teil stehen, damit es auf alle Fälle ausgeführt wird. Da auch Close() schiefgehen kann brauchen wir nochmal ein try-catch.


Die Variante mit try-finally, die durch using ersetzt wird

Diese Variante führt uns zur Verwendung des using-Konstrukts. Neben der Close()-Methode gibt es bei allen Streamklassen in der IO-Hierarchie die Methode Dispose(). Sie wird im Interface IDisposable vereinbart. Wie man der Graphik entnehmen kann wird dieses Interface von allen Dateihandlingklassen implementiert. Die Methode Dispose() macht das gleiche wie Close(), wirft aber keine Exception (laut API), so daß in finally nicht erneut ein try-catch stehen muß. Das sieht folgendermaßen aus.

using System;
using System.IO;

namespace io
{
   class StreamWriterDemo
   {
      static void Main()
      {
         StreamWriter sw = new StreamWriter("test.txt");
         try
         {
            sw.Write("foo");  // Bearbeiten
         }
         finally
         {
            if(sw!=null) ((IDisposable)sw).Dispose();  // Schließen
            // Dispose() von Object wird gerufen, Dispose() wirft keine Exception
         }
      }  // end Main
   }
}

Hier wird in finally explizit die Methode Dispose() aus der Klasse Object gerufen. Das obige Konstrukt ist äquivalent zu der folgenden using-Anweisung.

using System;
using System.IO;

namespace io4
{
   class StreamWriterDemo
   {
      static void Main()
      {
         using(StreamWriter sw = new StreamWriter("test.txt") ) // Öffnen
         {
            sw.Write("foo");  // Bearbeiten
         }
      }
   }
}

Tatsächlich übersetzt der Compiler die using-Anweisung exakt in das vorhergehende try-finally Konstrukt. (siehe etwas Understanding the 'using' statement in C#) Welche Schreibweise man wählt ist letzten Endes Ansichtssache. Ein Vorteil ist jedoch, daß die durch die Verwendung von using automatisch aufgerufene Methode Dispose() keine Exception wirft, Close() jedoch schon.


Der Unterschied zwischen den Methoden Close() und Dispose()

Für die Klassen aus dem Namespace IO haben die Methoden Dispose() und Close() die gleiche Wirkung, siehe dazu den Artikel von Kim Hamilton im MSDN Blog. Bei Datenbankverbindungen kann es jedoch Unterschiede geben, vgl. hierzu die beiden Artikel auf stackoverflow.com Artikel 1 und Artikel 2 und auch Artikel 3.

Der letzte Artikel zeigt insbesondere, daß die Methoden Close() von StreamReader und StreamWriter Dispose() aufrufen.


Eventuelle Nachteile der using-Anweisung

Bei der Einführung der using-Anweisung ging es eben nicht um Fehlerbehandlung, sondern man wollte ein Konstrukt, das durch den automatischen Aufruf von Dispose() kein finally mehr braucht.


und wie man sie vermeiden kann

Braucht man eine Fehlerbehandlung, so gibt es mehrere Möglichkeiten


Einbetten von using in try-catch
try
{
   using (StreamReader sr = new StreamReader("test.txt")) // Öffnen
   {
      string line = sw.ReadLine();  // Einlesen
   }
}
catch (Exception ex)
{
   // Fehlerbehandlung
}

Beim Öffnen eines StreamReaders können die folgenden 5 Exceptions auftreten, auf die man in catch() reagieren kann.

ArgumentException (path is an empty string).
ArgumentNullException (path is null)
FileNotFoundException (The file cannot be found)
DirectoryNotFoundException (The specified path is invalid, such as being on an unmapped drive)
IOException (path includes an incorrect or invalid syntax for file name, directory name, or volume label)

Eine andere Möglichkeit ist eine Überprüfung mit den Utilityklassen File bzw. Directory.


Prüfungen vor using
...
if (filename != null && filename != "" && Directory.Exists(filename) && File.Exists(filename))
  using (StreamReader sr = new StreamReader(filename))
  {
    // kleine Textdateien auf einmal lesen
    string text = sr.ReadToEnd();
    Console.WriteLine(text);
  }

Verzicht auf using und Verwenden von try-catch-finally
StreamWriter sw = null;
try
{
   sw = new StreamWriter("test.txt"); // Öffnen
   sw.Write("foo");  // Bearbeiten
}
catch (Exception ex)
{
   Console.WriteLine(ex.Message);
}
finally
{
   if(sw!=null) ((IDisposable)sw).Dispose();
}

In diesem Fall muß man selbst darauf achten, daß in finally die Dispose()-Methode von Objekt aufgerufen wird.
Interessant zu diesem Thema ist auch die Diskussion auf social.msdn.microsoft.com

Valid XHTML 1.0 Transitional top Back Next Up Home