Feedback

C# - INI-Datei auslesen

Veröffentlicht von am 05.01.2013
(0 Bewertungen)
Mit diesem Snippet kann man ganz einfach eine
INI-Datei auslesen. Die INI-Datei wird in einem Dictionary gespeichert.

// NAMESPACE(S):
using System.Collections;
using System.IO;


// ZUGRIFF (Beispiel):
Hashtable meineINI = holeIniDateiAlsHashtable(@"C:\Users\Falcone\Desktop\konfiguration.ini", 2, '=', '#');


Um den Wert eines Schlüssels in einer Hashtable herausfinden zu können...
http://dotnet-snippets.de/dns/wert-aus-einer-hashtable-ueber-schluessel-herausfinden-SID1663.aspx



ACHTUNG:
Sollten Werte in der INI-Datei Base64 verschlüsselt abgespeichert werden,
sollte vorher das Paddingzeichen (meistens '=') durch ein anderes Zeichen (z. B. '+')
ersetzt werden, da der Standard-Seperator einer INI-Datei meistens auch '=' ist.

Meine config.ini:
config.ini
**********
# Infos ueber den Benutzer
# ************************
vorname=max
nachname=mustermann
geburtsdatum=01.01.1990
geburtsort=muenchen

# Anmelde-Variablen (verschluesselt:)
# ***********************************
username=4fFs2gRy
password=zjH05GhE

# Soll das Passwort gespeichert werden (0 = nein / 1 = ja)
savePassword=1

# Der Pfad zum hintergrundbild der Windows Forms Anwendung
bgpath=C:\Users\Falcone\Desktop\meinBild.jpg

# Allgemeine Regel fuer INIs
key=value
/// <summary>
        /// Gibt den Inhalt einer INI-Datei als Dictionary zurueck
        /// ******************************************************
        /// </summary>
        /// <param name="pfad">Der Pfad zur INI-Datei</param>
        /// <param name="anzahlKopfzeilen">Gegebenenfalls Kopfzeilen ueberlesen</param>
        /// <param name="trennzeichen">
        /// Das Trennzeichen in der INI-Datei (meistens das Gleichzeichen)
        /// ACHTUNG! Darf weder im Schluessel noch im Wert vorkommen!!! 
        /// </param>
        /// <param name="kommentarzeichen">
        /// Wenn das Zeichen am Anfang einer Zeile steht wird diese auskommentiert
        /// </param>
        /// <returns></returns>
        public List<Dictionary<string,string>> holeIniDateiAlsDictionaryList(string pfad = "", ushort anzahlKopfzeilen = 0, char trennzeichen = '=', char kommentarzeichen = '#')
        {
            // Variablen um INI-Datei auszulesen
            string aktuelleZeile = ""; // Die Zeile die gerade aus der Datei gelesen wird
            string schluessel = ""; // linke Seite vom Seperator (Schluessel)
            string wert = ""; // rechte Seite vom Seperator (Wert)
            string[] daten = new string[2]; // [0] = Schluessel / [1] = Wert
            Dictionary<string,string> sektionen = new Dictionary<string,string>(); // Erstelle eine Tabelle (Schluessel:Wert)
            List < Dictionary<string, string>> inidatei = new List<Dictionary<string,string>>(); // Erstelle eine Liste mit Tabellen / Sektionen

            // Testen ob pfad existiert
            if (File.Exists(pfad))
            {
                try
                {
                    
                    // oeffne Datei ueber pfad (lesender Zugriff)
                    using (StreamReader sr = new StreamReader(pfad)) // StreamReader im using oeffnen da IDisposable (damit nach einem Programmabsturz noch auf die Datei zugreifen kann)
                    {
                        // ueberlese Kopfzeilen
                        for (int i = 0; i < anzahlKopfzeilen; i++) // lese solange bis die gewollte anzahl der Kopfzeilen ueberlesen wurde
                        {
                            sr.ReadLine(); // lese Zeile... (ohne abzuspeichern!)
                        }

                        // lese restliche Datei als ini (da Kopfzeilen gerade ueberlesen wurden)
                        while (!sr.EndOfStream) // solange wie das Ende der Datei nicht erreicht ist...
                        {
                            // Zeile lesen
                            aktuelleZeile = sr.ReadLine().TrimStart(" ".ToCharArray()); // speichere aktuelle Zeile ohne Anfangs-Leerzeichen in Variable

                            // wenn Zeile nicht leer, keine Kommentarzeile & keine Sektion ist
                            if (!string.IsNullOrEmpty(aktuelleZeile) && Convert.ToChar(aktuelleZeile.Substring(0, 1)) != kommentarzeichen && (aktuelleZeile.Substring(0, 1) != "[" && aktuelleZeile.Substring(aktuelleZeile.Length - 1, 1) != "]"))
                            {
                                // Zeile korrekt verarbeiten
                                daten = aktuelleZeile.Split(trennzeichen); // trenne Schluessel und Wert per Trennzeichen und speichere die beiden getrennt ab
                                schluessel = daten[0]; // Schluessel in Variable abspeichern
                                wert = daten[1]; // Wert in Variable abspeichern

                                // in Tabelle einpflegen
                                sektionen.Add(schluessel, wert);
                            }
                            else if (aktuelleZeile.Substring(0, 1) == "[" && aktuelleZeile.Substring(aktuelleZeile.Length -1,1) == "]") // Sektion!
                            {
                                // War dictionary gefuellt? Wenn ja:
                                if (sektionen.Count > 0)
                                {
                                    // Neue Sektion hinzufuegen:
                                    inidatei.Add(sektionen); // in Liste einfuegen  
                                    sektionen.Clear(); // neues Dictionary beginnen (altes dictionary leeren)

                                    // Sektionsschluessel hinzufuegen
                                    sektionen.Add("[SECTION]", aktuelleZeile); 
                                }
                            }
                        }
                        // Stream schliessen
                        sr.Close();
                    }
                }
                catch (IndexOutOfRangeException) // Falls zuviele werte im datenarray gespeichert werden sollten
                {
                    MessageBox.Show("Bitte ueberpruefen Sie ob die Ini-Datei wohlgeformt ist.", "Fehler bei der Datenverarbeitung");
                }
                catch (Exception exc) // Falls ein unbekannter Fehler auftreten sollte
                {
                    MessageBox.Show("Nachricht:\n\n" + exc.Message + "\n\n\n" + "Stackverfolgung:\n\n" + exc.StackTrace, "Ein unerwateter Fehler ist aufgetreten");
                }
            }
            else
            {
                // Falls der pfad nicht existiert
                MessageBox.Show("Die Datei wurde nicht gefunden, bitte ueberpruefen Sie den Dateipfad nochmal.", "Fehler bei dem einlesen der Datei");
            }
            // Tabelle zurueckgeben um spaeter damit weiter arbeiten zu koennen
            return inidatei;
        }

14 Kommentare zum Snippet

Scavanger schrieb am 06.01.2013:
Ach herrje...

Mal ein paar Verbesserungsvorschläge (an einen hoffnungsvollen Anfänger ;) ):

1. == true / == false ist unnötig siehe: http://www.mycsharp.de/wbb2/thread.php?threadid=17536
2. ushort ist ein Werttyp und kann nie null sein, die Prüfung ist unnötig.
3. Der Streamreader muss wieder geschlossen werden. Stichwort: using Schlüsselwort / streamreader.close()
4. Statt die Argumente zu prüfen und dann Standardwerte zuzuweisen gibt's es die Möglichkeit optionale Argumente im Methodenheader anzugeben:
public Dictionary<string,string> holeIniDateiAlsDictionary(string pfad, ushort anzahlKopfzeilen = 0, char trennzeichen = '=', char kommentarzeichen = '#')

5. Du solltest Prüfen ob die Datei wirklich existiert (File.Exist()) und wenn niht eine Exception werfen was zu
6. Fehlerbehandlung führt: Gerade bei Streams können immer wieder unerwartete Fehler auftauchen, daher ist eine Fehlerbehandlung mit try .. catch Pflicht.
7. Du musst nicht immer die Werte in Variablen zwischenspeichern:
8. Wenn ein Argument ungültig ist (z.B. pfad null oder leer) nicht einfach ein leeres Dictionary zurückgeben sondern eine ArgumentException werfen.
9. Was macht dein Code wenn die INI-Datei falsch formatiert ist?

Das zum Programmierstil, noch etwas zum Format von ini Dateien:

- Ini-Dateien können Sections haben ([Section]) innerhalb von verschieden Sections können gleiche Schlüssel vorkommen .

Beispiel:

[User1]
Name=Andreas
Nachname=Mayer

[User2]
Name=Michael
Nachname=Müller
hackman schrieb am 06.01.2013:
Argh

Bei jedem Dateizugriff immer ein using - Statement verwenden. Wenn man den code abbricht, und nochmals neu startet, dann kann auf einmal die datei nicht geöffnet werden ...

StreamReader ist ein IDisposable, und daher IMMER in einem using Zweig verwenden!!!!
Falcone schrieb am 11.01.2013:
Vielen Dank für eure Verbesserungsvorschläge Scavanger & hackman,
ich habe versucht sie so gut ich es wusste umzusetzen.

@Scavenger
1. == true / false habe ich immer aus Übersichtlichkeitsgründen verwendet, aber wenn das schneller läuft dann nehme ich es gerne.
2. http://www.youtube.com/watch?v=5jhOy0_Regg
3. siehe 2.
4. Cool den kannte ich noch gar nicht. Ist es in Klassen auch sinnvoller Setter durch optionale Argumente zu ersetzen?
5. siehe 2.
6. Danke habe ich integriert, normalerweise benutze ich try-catch aber nur bei Benutzereingaben...
7. Das mache ich aus Übersichtlichkeitsgründen so.
8. Gute idee, habe ich nun aber mit einer Messagebox gelöst, da ich lieber selbst formuliere was passiert ist.
9. Ich weiß, dass man eine XML auf wohlgeformtheit prüfen kann (geschieht beim öffnen glaub ich automatisch)
aber wie man das bei einer ini feststellt weiß ich nicht. Habe jetzt eine ArrayOutOfRange falls in einer Zeile mehrmals der Seperator
vorkommen sollte

10. Sections habe ich jetzt ein wenig unschön realisiert (kannte ich vorher auch nicht),
da ich nur davon ausgehe, dass die Zeile mit '[' beginnt und mit ']' endet

@hackman
Danke für den Tipp, ich habe es integriert.
Finde ich aber irgendwie nicht schön...

Was passiert denn, wenn ich z. B. noch ein FileStream oder einen StreamWriter habe möchte?
Muss ich dann für jeden Stream einen neuen using Block in die Funktion schreiben?

Mit freundlichen Grüßen
Falcone
Falcone schrieb am 11.01.2013:
Da fällt mir noch ein
List<Dictionary<string,string>>

ist ebenfalls ein wenig unschön.
Soll ich daraus eine Arraylist machen oder gibt es einen geeigneteren/schöneren Datentyp?

Mit freundlichen Grüßen
falcone
dariusarnold schrieb am 13.01.2013:
Warum muss man StreamReader immer in einem Using-Zweig verwenden?? Ich verstehe das nicht.. :/

Ich mach immer einfach:

if (File.Exists("file.txt"))
{
StreamReader ReadFile = new StreamReader("file.txt");
try
{
string zeile 1 = ReadFile.ReadLine();
string zeile 2 = ReadFile.ReadLine();
}
catch
{
MessageBox.Show("Misst!! ;)");
}
}
ReadFile.Close(); // So wird der Reader auf jeden Fall gschlossen..


Außerdem:
Ich weiß nicht was für du für einen Compiler verwendest, aber zumindest - ich sag einfach mal 'der von Visual Studio 2010' zeigt einem ja auch sofort an wenn man z.B. schreibt
if (1 == 0)
MessageBox.Show("Wenn man das sieht, ist ganz schön was falsch gelaufen");
,
dass er 'Unereichbaren Code' gefunden hat.. Glaubst du er compiliert das mit?? Der biegt sich das zu recht, dass es möglichst klein ist :) Deshalb.. kein Problem wenn man == true oder == false schreibt.

LG
dariusarnold schrieb am 13.01.2013:
Ups.. PS:
Das ReadFile.Close(); muss natürlich noch mit in den If-Block.. :)
aha47 schrieb am 14.01.2013:
@dariusarnold: Bitte nicht File.Exists verwenden. Besser ist es, die möglichen Exceptions beim Stream-Zugriff abzufangen. Außerdem immer immer immer

using (StreamReader sr = new StreamReader(...)
{
...
}
verwenden.
dariusarnold schrieb am 14.01.2013:
Das hab ich ja gefragt.. Hättest du auch sehen müssen!!

Was sind die Vorteile von using() ??

LG
Scavanger schrieb am 15.01.2013:
Servus,

besser:

- True/False: Das ist kein "Stil", das zeigt einfach das du das Konzept dahinter nicht verstanden hast. Dadurch läuft es auch nicht schneller: Der Compiler optimiert es eh weg.
Es schreibt ja auch kein Mensch:
if ((foo == 1) == true) 



- An einer List<Dictionary<string,string>> finde ich nichts verwerfliches. Hätte ich auch genommen.

- using: Bitte mach dich selber schlau, die MSDN ist eine riesige Fundgrube voller Wissen http://msdn.microsoft.com/de-de/library/yh598w02(v=vs.100).aspx

@aha47:
@dariusarnold: Bitte nicht File.Exists verwenden. Besser ist es, die möglichen Exceptions beim Stream-Zugriff abzufangen.

Blödsinn:
Dann hast du das Konzpet des Exceptions nicht verstanden.
Eine Exception zu werfen und wieder zu fangen ist sehr langsam. Eine Exception ist dafür gedacht unerwartete(!) Fehler abzufangen. Eine nicht vorhandene Datei ist nicht unerwartet (man kann prüfen ob sie vorhanden ist).



spezi schrieb am 02.02.2013:
Einige Vergleiche wie
Convert.ToChar(aktuelleZeile.Substring(0, 1)) != kommentarzeichen

und
aktuelleZeile.Substring(aktuelleZeile.Length - 1, 1) != "]"


kannst du auch noch vereinfachen zu:
aktuelleZeile.First() != kommentarzeichen

und
aktuelleZeile.Last() != ']'

Dazu ist der namespace System.Linq notwendig.

Alterative ohne Linq Extensions:
aktuelleZeile[0] != kommentarzeichen

und
aktuelleZeile[aktuelleZeile.Length - 1] != ']'




Davon abgesehen bin ich kein Freund von MessageBoxen im Logik-Code. Was ist, wenn du den Code in einem Konsolenprogramm benutzen willst? Ich würde die Exceptions extern behandeln, die evtl. niedrigere Performance wird in den meisten Fällen keine Rolle spielen.
C#phi schrieb am 17.04.2014:
Hallo,

ich bin totaler C# Anfänger. Habe jetzt mit dem Projekt programmieren gestartet und bin auf die INI Datei gestoßen.

In meinem Program möchte ich genau eine solche INI Datei verwenden. In meiner INI Datei steht in Zeile1 Name1=192.168.0.1 in Zeile 2 Name2=192.168.0.2 usw. usw.

Jetzt möchte ich eine einfache Funktion die meine INI Datei öffnet Zeile für Zeile liest und trennt so dass ich im Programm iwi die Variable "Name1" einsetzen kann und dann der dazu gehörige Wert (192.168.0.1) ausgegeben wird (z.B. Messagebox)

Ich kann mir das allerdings noch nicht funktionstüchtig (ohne Fehler) zusammen bauen.

Falcone dein Code ist zwar schon recht verständlich allerdings bräuchte ich es glaube ich noch verständlicher :-(

Könnt Ihr mir da helfen??
Koopakiller schrieb am 17.04.2014:
Hallo C#phi,
Wenn deine Datei in etwa folgenden Inhalt hat:
Name1=127.0.0.1
Name2=127.0.0.2
Name3=127.0.0.3

Dann ist das nur bedingt eine richtige INI-Datei, weil der Sektionsname fehlt. (Das was in [] steht)
Das macht es aber um so einfacher über LINQ ein Wörtrbuch daraus zu erstellen:
var dict = System.IO.File.ReadAllLines("Dateiname.ini")//Ab .NET 4.0: benutze ReadAllLines(string) 
.Where(x=>!x.Startswith("#"))
.Select(x=>x.Split(new char[]{'='}, 2)
.ToDictionary(x=>x[0],x=>x[1]);

File.ReadLines ließt alle zeilen der Datei ein. Die Where-Methode überprüft ob eine Zeile mit # beginnt. Wenn das der Fall ist, wird es ignoriert (ist in meinem Fall ein Kommentar). Die restlichen Zeilen werden einfach weiter gegeben. Select wandelt anschließend die Zeilen in ein je ein Array mit 2 Elementen (Schlüssel und Wert) um. ToDictionary erzeugt daraus wiederrum eine Instanz von Dictionary<string,string>. Dadurch kannst du nun einfach über dict["Name1"] an den Wert heran kommen.

Das dürfte wohl das einfachste für dich sein. LINQ ist etwas sehr komplexes, aber hier brauchst du nur die Basics. Soweit ich weiß sind die Methoden alle ab .NET 3.5 verfügbar.
C#phi schrieb am 20.04.2014:
Hallo Koopakiller,

vielen Dank erst mal. Ich habe jetzt nur ein Problem mit dem Aufrufen des Wertes.

Habe versucht über dict an den Wert heran zu kommen, allerdings kommt bei mir die Meldung, dass dict im aktuellen Kontext nicht vorhanden ist.

Kannst Du mir vielleicht einen genauen Code Aufruf sagen. Vielleicht mache ich auch nur was falsch.

Koopakiller schrieb am 21.04.2014:
Hallo C#phi,
In dem 2. Codeblock erstelle ich mit LINQ aus der Datei das Wörterbuch dict. Nach diesen 3 Zeilen kannst du auf dict zugreifen, da sollte es keine Programe geben. Dabei muss die Abfrage von dict natürlich im selben Gültigkeitsbereich, d.h. innerhalb der selben Methode o.ä., erfolgen,
Wenn es noch immer nicht klappt, poste bitte den Codeausschnitt mit dem LINQ-Ausdruck bis hin zum Abfragen von dict. Außerdem nochmal die exakte Fehlermeldung.
 

Logge dich ein, um hier zu kommentieren!