Programmieren lernen in Visual C#.NET

 
Programmieren lernen in Visual C#.NET
                                 von
                   Walter Doberenz, Thomas Kowalski

                                 1. Auflage

                         Hanser München 2003

                        Verlag C.H. Beck im Internet:
                                www.beck.de
                          ISBN 978 3 446 22023 2

                          Zu Inhaltsverzeichnis

schnell und portofrei erhältlich bei beck-shop.de DIE FACHBUCHHANDLUNG
CARL HANSER VERLAG

   Walter Doberenz, Thomas Kowalski

Programmieren lernen in Visual C#.NET

             3-446-22023-2

            www.hanser.de
8.1 Operationen mit Verzeichnissen und Dateien                                                  201

Zu den wichtigsten Aufgaben des Programmierers zählt es, Daten auf der Festplatte (Diskette, CD,
...) permanent abzuspeichern bzw. von dort zu laden. Das .NET-Framework stellt dafür im
System.IO-Namensraum eine Anzahl leistungsfähiger Klassen zur Verfügung, die diese Aufgabe
vereinfachen sollen. Diese Lektion vermittelt Ihnen die dazu erforderlichen Grundkenntnisse.

8.1 Operationen mit Verzeichnissen und Dateien
Bevor wir uns dem Lesen und Schreiben von Dateien zuwenden, betrachten wir die Arbeit auf Ver-
zeichnisebene, womit das Erstellen, Löschen, Kopieren, Verschieben, Umbenennen, Durchsuchen
und Überwachen von Verzeichnissen und Dateien gemeint ist.

8.1.1 Klassen für Verzeichnis- und Dateioperationen
Für Manipulationen mit Verzeichnissen und Dateien gibt es neben der Path-Klasse die Pärchen
Directory/DirectoryInfo und File/FileInfo, die sich vor allem hinsichtlich ihrer Instanziierbarkeit
unterscheiden (statische oder Instanzen-Methoden, siehe 8.1.11). Alle fünf Klassen befinden sich im
System.IO-Namensraum.

Klasse                  Beschreibung
Directory               ... die statischen Methoden erlauben das Erstellen, Verschieben und
                        Benennen von Verzeichnissen und Unterverzeichnissen.
DirectoryInfo           ... ähnelt der Directory-Klasse, enthält aber nur Instanz-Methoden.
Path                    ... die statischen Methoden erlauben die plattformübergreifende Arbeit mit
                        Verzeichnissen.
File                    ... die statischen Methoden erlauben das Erzeugen, Kopieren, Löschen,
                        Verschieben und Öffnen von Dateien.
FileInfo                ... ähnelt der File-Klasse, enthält aber nur Instanz-Methoden.
FileSystemWatcher       ... löst Ereignisse zum Überwachen des Dateisystems aus.

Hinweis: Methoden der Klassen File und FileInfo liefern auch die Voraussetzungen für den
         Schreib- und Lesezugriff auf Dateien (siehe 8.2).

Bevor wir uns der Anwendung dieser Klassen zuwenden, soll im folgenden Abschnitt noch ein
generelles Problem geklärt werden.

8.1.2 Zur Schreibweise von Verzeichnissen in C#
Eine "normale" Schreibweise von Verzeichnissen, wie z.B. "c:\temp", ist in C# nicht möglich, da
der Slash (\ bzw. /) als Steuerzeichen (Escape-Sequenz) gedeutet wird. Generell gibt es deshalb zwei
Alternativen:
  ■    Verwenden des doppelten Slash, z.B. "c://temp"
  ■    Voranstellen des Zeichens "@", z.B. @"c:/temp"
202                                                      LEKTION 8: Dateien und Streams

Hinweis: Ob der normale Slash (/) oder der Backslash (\) verwendet wird, ist bedeutungslos.

8.1.3 Verzeichnisse erzeugen und löschen

Mit Directory-Klasse
Die einfachsten Möglichkeiten zum Erzeugen und Löschen von Verzeichnissen bieten die statischen
Methoden CreateDirectory() und Delete() der Directory-Klasse.

Beispiel: Ein Verzeichnis erzeugen und anschließend wieder löschen
string path=@"c:\temp";
Directory.CreateDirectory(path);    // falls Verzeichnis bereits vorhanden, passiert nichts!
...
Directory.Delete(path,true);        // löscht auch vorhandene Unterverzeichnisse und Dateien

Mit DirectoryInfo-Klasse
Das gleiche Ziel, allerdings etwas umständlicher, erreicht man mit der Create-Methode der
DirectoryInfo-Klasse, wobei mittels CreateSubdirectory auch das Hinzufügen von Unterverzeich-
nissen möglich ist.

Beispiel: Es werden ein Verzeichnis und ein Unterverzeichnis angelegt und anschließend wieder
gelöscht.
DirectoryInfo di = new DirectoryInfo(@"c:\temp");
di.Create();
di.CreateSubdirectory("temp1");
...
di.Delete(true);                // löscht inklusive vorhandener Unterverzeichnisse und Dateien

Hinweis: Der parameterlose Aufruf von Delete() funktioniert nur, wenn das Verzeichnis leer ist!

8.1.4 Verzeichnisse verschieben und umbenennen
Für diese Aufgaben verwenden Sie am besten die Move-Methode der statischen Directory-Klasse.

Beispiel: Das Verzeichnis c:\tempX wird verschoben und umbenannt nach c:\beispiele\tempY.
Directory.Move(@"c:\tempX", @"c:\beispiele\tempY");

8.1.5 Das aktuelle Verzeichnis ermitteln bzw. festlegen
Verwenden Sie dazu die GetCurrentDirectory- bzw. SetCurrentDirectory-Methode der (statischen)
Directory-Klasse.
8.1 Operationen mit Verzeichnissen und Dateien                                                203

Beispiel: Festlegen und Anzeigen des aktuellen Arbeitsverzeichnisses
Directory.SetCurrentDirectory(@"c:/test");
label1.Text = Directory.GetCurrentDirectory();       // zeigt "c:/test"

Hinweis: Wenn der Dateiname ohne Pfad angegeben wird, bezieht sich die Datei automatisch auf
         das Projekt- bzw. Arbeitsverzeichnis.

Beispiel: Im Projektverzeichnis wird ein Verzeichnis \temp angelegt.
Directory.CreateDirectory("temp");

8.1.6 Unterverzeichnisse ermitteln
Um alle Unterverzeichnisse zu ermitteln, verwenden Sie die GetDirectories-Methode der Directory-
Info-Klasse.

Beispiel: Es werden alle Unterverzeichnisse von c:\ in einer ListBox angezeigt.
DirectoryInfo myDir = new DirectoryInfo(@"c:\"); // Erzeugen eines neuen DirectoryInfo-Objekts
DirectoryInfo[] myDirs;                // Array zum Speichern der Unterverzeichnisse anlegen
myDirs = myDir.GetDirectories();       // alle Unterverzeichnisse ermitteln und im Array
abspeichern
for (int i = 0; i < myDirs.Length; i++) // alle Unterverzeichnisse durchlaufen ...
{
   listBox1.Items.Add(myDirs[i].Name);    // ... und Verzeichnisnamen zur ListBox hinzufügen
}

Hinweis: Mit der GetFiles-Methode können Sie alle in einem Verzeichnis enthaltenen Dateien
         bestimmen.

Das komplette Beispiel finden Sie auf der Buch-CD.

8.1.7 Anwenden der Path-Klasse
Die Anwendung der Methoden der statischen Path-Klasse soll anhand eines Beispiels demonstriert
werden.

Beispiel: Ausgabe von Dateiinfos in einer ListBox
string verz = @"c:\test\info.txt";
listBox1.Items.Add("Verzeichnis : " + Path.GetDirectoryName(verz));
listBox1.Items.Add("Dateiname : " + Path.GetFileName(verz));
listBox1.Items.Add("Dateiname ohne Extension : " + Path.GetFileNameWithoutExtension(verz));
listBox1.Items.Add("Dateiextension : " + Path.GetExtension(verz));
listBox1.Items.Add("Rootverzeichnis : " + Path.GetPathRoot(verz));
204                                                     LEKTION 8: Dateien und Streams

listBox1.Items.Add("Temporäres Verzeichnis : " + Path.GetTempPath());
listBox1.Items.Add("Neues Tempfile : " + Path.GetTempFileName());

Hinweis: Die meisten Member der Path-Klasse wirken nicht mit dem Dateisystem zusammen
         und überprüfen deshalb nicht, ob die durch eine Pfadzeichenfolge angegebene Datei
         tatsächlich vorhanden ist.

8.1.8 Dateien kopieren, verschieben und umbenennen

Kopieren und verschieben
Am einfachsten realisieren Sie diese Aufgabe mit den statischen Copy- bzw. Move-Methoden der
File-Klasse.

Beispiel: Datei kopieren und anschließend verschieben
string sourcePath=@"c:\sample.txt";
string destPath=@"c:\sample1.txt";
string movePath=@"c:\temp\sample1.txt";

File.Copy(sourcePath,destPath);
File.Move(sourcePath,movePath);
Falls Sie lieber mit Instanzen arbeiten, können Sie die Methoden CopyTo und MoveTo der Klassen
FileInfo bzw. DirectoryInfo verwenden.

Hinweis: Auch die Directory-Klasse verfügt über eine Move-Methode zum Verschieben von
         Verzeichnissen.

Umbenennen
Das .NET-Framework bietet keine Möglichkeit zum direkten Umbenennen einer Datei, da die
Name-Eigenschaft der FileInfo-Klasse schreibgeschützt ist und eine Rename-Methode fehlt.
Verwenden Sie zum Umbenennen also die Methoden Move der Klasse File bzw. MoveTo der
Klasse FileInfo.

Beispiel: Umbenennen der Datei info.txt in info_1.txt
FileInfo fi = new FileInfo(@"c:\test\info.txt");
fi.MoveTo(@"c:\test\info_1.txt");
8.1 Operationen mit Verzeichnissen und Dateien                                                         205

8.1.9 Dateiattribute feststellen

FileAttributes-Enumeration
Die verschiedenen Attribute für Dateien und Verzeichnisse sind in der FileAttribute-Enumeration
anzutreffen. Die Tabelle zeigt die wichtigsten:

Mitglied             Beschreibung
Archive              ... entspricht dem Archiv-Status der Datei, wie er oft zum Markieren einer zu
                     löschenden oder einer Backup-Datei verwendet wird.
Compressed           ... entspricht einer gepackten Datei.
Directory            ... zeigt an, dass die Datei in Wirklichkeit ein Verzeichnis ist.
Encrypted            ... die Datei ist verschlüsselt.
Hidden               ... die Datei ist versteckt und demzufolge in einem gewöhnlichen Verzeichnis
                     unsichtbar.
Normal               ... es wurden keine Datei-Attribute gesetzt.
ReadOnly             ... die Datei kann nicht verändert, sondern nur gelesen werden.
System               ... die Datei gehört zum Betriebssystem oder wird exklusiv von diesem
                     benutzt.
Temporary            ... die Datei ist temporär, d.h., sie wird vom Programm bei Bedarf angelegt
                     und wieder gelöscht.

Wichtige Eigenschaften und Methoden
Um die Dateiattribute zu ermitteln, kann man entweder auf die Eigenschaften der FileInfo-Klasse
oder aber auch auf die entsprechenden (statischen) Methoden der File-Klasse zugreifen:

Eigenschaft          Methode                        Beschreibung
FileInfo-Klasse      File-Klasse
Attributes           GetAttributes()                ... Wert basiert auf Datei-Attribute-Flags
                     SetAttributes()                (archive, compressed, directory, hidden, ...) .
CreationTime         GetCreationTime()              ... Datum/Zeit der Erstellung.
                     SetCreationTime()
LastAccessTime       GetLastAccessTime()            ... Datum/Zeit des letzten Zugriffs.
                     SetLastAccessTime()
LastWriteTime        GetLastWriteTime()             ... Datum/Zeit des letzten Schreibzugriffs.
                     SetLastWriteTime()
Exists               Exists()                       ... liefert true, falls Datei physikalisch existiert.

Beispiel: Feststellen, ob Datei im Arbeitsverzeichnis existiert
if (File.Exists("liesmich.txt")) MessageBox.Show("Datei ist vorhanden!");
206                                                             LEKTION 8: Dateien und Streams

oder
FileInfo fi = new FileInfo("liesmich.txt");
if (fi.Exists) MessageBox.Show("Datei ist vorhanden!");

Beispiel: Anzeige des Erstellungsdatums einer Datei
label1.Text = File.GetCreationTime("liesmich.txt").ToString();

Beispiel: In zwei CheckBoxen wird angezeigt, ob es sich um eine normale oder um eine Archiv-
Datei handelt.
FileAttributes attbs = File.GetAttributes("c://beispiele//test.dat");
if (attbs==(attbs|FileAttributes.Normal)) checkBox1.Checked = true;
else checkBox1.Checked = false;
if (attbs==(attbs|FileAttributes.Archive)) checkBox2.Checked = true;
else checkBox2.Checked = false;

Das komplette Beispiel finden Sie in Übung 8.1.

8.1.10 Weitere Datei-Eigenschaften
Die folgende Tabelle zeigt einige weitere wichtige Eigenschaften der FileInfo-Klasse.

Eigenschaft                  Beschreibung
FileInfo-Klasse
Directory                    ... liefert Instanz des übergeordneten Verzeichnisses.
DirectoryName                ... liefert den vollständigen Dateipfad.
Extension                    ... liefert Dateiextension (z.B. txt für Textdateien).
FullName                     ... liefert vollständigen Dateipfad plus Dateinamen.
Length                       ... liefert Dateigröße in Bytes.
Name                         ... liefert Dateinamen.

Beispiel: Der Pfad der Datei liesmich.txt wird in einem Label angezeigt.
FileInfo fi = new FileInfo("liesmich.txt");
label1.Text = fi.DirectoryName;

Im Zusammenhang mit der Directory-Eigenschaft der FileInfo-Klasse verdient die GetFileSystem-
Infos-Methode der DirectoryInfo-Klasse besondere Beachtung.

Beispiel: In einer TextBox werden neben dem Verzeichnis einer Datei alle weiteren sich im
gleichen Verzeichnis befindlichen Dateien und Unterverzeichnisse angezeigt.
FileInfo fi = new FileInfo("liesmich.txt");     //     öffnet existierende Datei oder erzeugt neue
DirectoryInfo di = fi.Directory;                //     Verzeichnis-Instanz erzeugen
FileSystemInfo[] fsi = di.GetFileSystemInfos();        // alle Einträge ermitteln
textBox1.Text = di.FullName + Environment.NewLine;     // vollständigen Verzeichnispfad anzeigen
8.1 Operationen mit Verzeichnissen und Dateien                                              207

foreach (FileSystemInfo info in fsi) // ... dann alle weiteren Unterverzeichnisse und Dateien
    textBox1.Text += info.Name + Environment.NewLine;

8.1.11 Statische oder Instanzen-Klasse?
Wenn Sie mit den Methoden der statischen Klassen File, Directory und Path arbeiten, werden
Sicherheitsüberprüfungen bei jedem Methodenaufruf vorgenommen, bei den Instanzen-Methoden
der Klassen FileInfo und DirectoryInfo geschieht dies nur ein einziges Mal.
Bei statischen Methoden müssen Sie jeder Methode den Dateinamen oder den Verzeichnispfad
übergeben. Das kann dann ziemlich lästig werden, wenn Sie diese Methoden öfters hintereinander
aufrufen müssen. Die entsprechenden Eigenschaften der Instanzen-Klassen FileInfo und Directory-
Info hingegen erlauben es Ihnen, den Datei- oder Verzeichnisnamen bereits im Konstruktor einmalig
zu spezifizieren.

Beispiel: Zwei alternative Möglichkeiten zum Anzeigen von Erstellungsdatum und Zeitpunkt des
letzten Zugriffs auf die Datei c:\test\info.txt
 label1.Text = File.GetCreationTime(@"c:\test\info.txt").ToString();
 label2.Text = File.GetLastAccessTime(@"c:\test\info.txt").ToString();
oder
 FileInfo myFile = new FileInfo(@"c:\test\info.txt");
 label1.Text = myFile.CreationTime.ToString();
 label2.Text = myFile.LastAccessTime.ToString();

Hinweis: In einigen Fällen haben Sie nur eine Wahl, wenn die gewünschte Eigenschaft/Methode
         nur von einer Klasse angeboten wird.

8.1.12 Überwachung von Änderungen im Dateisystem
Die Klasse FileSystemWatcher dient dem einfachen Beobachten des Dateisystems, so löst sie z.B.
Ereignisse aus, wenn Dateien oder Verzeichnisse geändert werden.

Beispiel: Das Überwachen von vier Ereignissen von .txt-Dateien im Verzeichnis c:\Beispiele:
  FileSystemWatcher watcher = new FileSystemWatcher(@"C:\Beispiele");
  watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.FileName;
  watcher.Filter = "*.txt";
  watcher.Changed += new FileSystemEventHandler(OnChanged);     // Datei wurde geändert
  watcher.Created += new FileSystemEventHandler(OnChanged);     // Datei wurde neu hinzugefügt
  watcher.Deleted += new FileSystemEventHandler(OnChanged);     // Datei wurde gelöscht
208                                                        LEKTION 8: Dateien und Streams

  watcher.Renamed += new RenamedEventHandler(OnRenamed);          // Datei wurde umbenannt
  watcher.EnableRaisingEvents = true;                             // Start der Überwachung
Die beiden Event-Handler spezifizieren die Reaktion auf die vier Ereignisse (in unserem Fall erfolgt
eine Anzeige in einer ListBox):
  public void OnChanged(object source, FileSystemEventArgs e)
  {
     listBox1.Items.Add("Datei: " + e.FullPath + " " + e.ChangeType);
  }

  public void OnRenamed(object source, RenamedEventArgs e)
  {
    listBox1.Items.Add("Datei: " + e.OldFullPath + " umbenannt in " + e.FullPath);
  }

Hinweis: Das vollständige Programm finden Sie auf der Buch-CD.

8.2 Lesen und schreiben von Dateien
In diesem Abschnitt geht es um das zentrale Anliegen der Lektion, nämlich um das Abspeichern
von Daten. Die wichtigsten Dateitypen sind:
  ■   Textdatei
  ■   Binärdatei (Bilddateien etc.)
  ■   sequenzielle Datei
  ■   Random-Access-Datei

Hinweis: Da .NET keine typisierten Dateien unterstützt, müssen sequenzielle und Random-
         Access-Dateien durch geeignete Programmiermaßnahmen auf Binärdateien zurückge-
         führt werden.

8.2.1 Übersicht

Dateien und Streams
Während man die in einer Datei gespeicherten Informationen auch als persistente Daten bezeichnet,
arbeitet das Programm mit temporären bzw. transienten Daten. Wie Sie der folgenden Abbildung
8.2 Lesen und schreiben von Dateien                                                                209

entnehmen, gewährleisten Streams quasi als "Verbindungskanäle" die Kommunikation zwischen
Datei und Programm.

                                     Programm                   temporäre Daten
                                                                (Arbeitsspeicher)

                            Stream

                                                       Stream
                     Read

                                               Write
                                                                persistente Daten
                                       Datei
                                                                   (Festplatte)

Klassen
Auch für Datei- und Stream-Operationen werden zunächst die Klassen File bzw. FileInfo benötigt
(siehe Abschnitt 8.1). Die folgende Tabelle zeigt die weiteren wichtigen Klassen.

Klasse               Beschreibung
FileStream           ... erlaubt, basierend auf einer Datei, das Erstellen einer Stream-Instanz.
StreamReader         ... implementiert ein TextReader-Objekt, welches Zeichen von einem Byte-
                     Stream in einer bestimmten Kodierung liest.
StreamWriter         ... implementiert ein TextWriter-Objekt, welches Zeichen in einen Stream in
                     einer speziellen Kodierung liest.
StringReader         ... implementiert ein TextReader-Objekt, das Daten von einem String liest.
StringWriter         ... implementiert ein TextWriter-Objekt, das Daten in einen String schreibt.
                     Die Daten werden in einer darunter liegenden StringBuilder-Klasse
                     gespeichert.
BinaryReader         ... erlaubt das binäre Lesen von Dateien.
BinaryWriter         .. erlaubt das binäre Schreiben in Dateien.
BinaryFormatter      ...kann Objekte in einen Stream serialisieren bzw. von dort deserialisieren.

Erzeugen einer Stream-Instanz
Voraussetzung für jeglichen Dateizugriff ist das Vorhandensein eines Stream-Objekts (siehe obige
Abbildung). Letzteres kann entweder über die Open-Methode eines FileInfo-Objekts oder der
statischen File-Klasse erzeugt werden.

Beispiel: Die Datei temp.txt soll für den exklusiven Schreib-/Lesezugriff geöffnet werden. Falls
sie nicht vorhanden ist, wird sie neu erzeugt.
FileInfo fi = new FileInfo("temp.txt");
FileStream fs = fi.Open( FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None );
210                                                         LEKTION 8: Dateien und Streams

oder
FileStream fs = File.Open("temp.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite,
FileShare.None);

Zur Bedeutung der einzelnen Parameter der Open-Methode kommen wir im folgenden Abschnitt.

8.2.2 Dateiparameter
In den Konstruktoren der Klassen File, FileInfo und FileStream werden bestimmte Parameter
übergeben, die in Enumerationen (Enums bzw. Aufzählungstypen) gekapselt sind.

FileAccess
Diese Enumeration bezeichnet den Zugriffslevel auf eine Datei.

Mitglied           Beschreibung
Read               ... erlaubt Lesezugriff
ReadWrite          ... erlaubt Lese- und Schreibzugriff
Write              ... erlaubt Schreibzugriff

Die FileMode-Enumeration
Diese Enumeration bestimmt den Öffnungsmodus einer Datei.

Mitglied           Beschreibung
Append             Eine existierende Datei wird geöffnet und der Dateizeiger an das Ende bewegt,
                   oder eine neue Datei wird erstellt ( FileAccess.Write ist erforderlich, Lesever-
                   suche schlagen fehl).
Create             Eine neue Datei wird erzeugt. Falls die Datei bereits existiert, wird sie
                   überschrieben.
Open               Eine existierende Datei wird geöffnet.
OpenOrCreate       Falls die Datei existiert, wird sie geöffnet, andernfalls wird sie neu erzeugt.
Truncate           Eine existierende Datei wird geöffnet und die Dateigröße auf null Bytes
                   beschnitten.

Die FileShare-Enumeration
Diese Enumeration verwenden Sie, um festzulegen, ob auf eine Datei gleichzeitig von mehreren
Prozessen aus zugegriffen werden kann.
Mitglied     Beschreibung
None         Die Datei ist für den gleichzeitigen Zugriff gesperrt. Alle weiteren Anforderungen
             zum Öffnen werden abgelehnt, es sei denn, die Datei ist geschlossen.
Read         Auch andere Benutzer bzw. Prozesse dürfen die Datei lesen. Versuche zum
             Schreiben bzw. Abspeichern schlagen fehl.
Sie können auch lesen
NÄCHSTE FOLIEN ... Stornieren