Przestrzeń nazw System.IO
Wszystkie operacje na plikach w C# znajdują się w przestrzeni nazw System.IO. Musimy ją dołączyć na początku programu:
using System.IO;Najważniejsze klasy do pracy z plikami:
- File – statyczna klasa do prostych operacji na plikach
- StreamReader – do odczytu plików tekstowych
- StreamWriter – do zapisu plików tekstowych
- FileStream – do zaawansowanych operacji na plikach
Słowniczek funkcji i ich działanie:
Klasa File (operacje na całych plikach):
File.ReadAllText(ścieżka)– odczytuje cały plik jako jeden stringFile.ReadAllLines(ścieżka)– odczytuje plik i zwraca tablicę stringów (każda linia = element)File.WriteAllText(ścieżka, tekst)– nadpisuje plik podanym tekstem (tworzy nowy jeśli nie istnieje)File.WriteAllLines(ścieżka, tablica)– nadpisuje plik tablicą stringów (każdy element = nowa linia)File.AppendAllText(ścieżka, tekst)– dopisuje tekst na końcu plikuFile.AppendAllLines(ścieżka, tablica)– dopisuje tablicę stringów na końcu plikuFile.Exists(ścieżka)– sprawdza czy plik istnieje (zwraca true/false)
Klasa StreamReader (odczyt linia po linii):
new StreamReader(ścieżka)– tworzy obiekt do czytania plikureader.ReadLine()– czyta jedną linię z pliku (zwraca null gdy koniec pliku)reader.ReadToEnd()– czyta resztę pliku jako jeden string
Klasa StreamWriter (zapis linia po linii):
new StreamWriter(ścieżka)– tworzy obiekt do pisania do pliku (nadpisuje)new StreamWriter(ścieżka, append: true)– tworzy obiekt do dopisywania na końcu plikuwriter.WriteLine(tekst)– zapisuje tekst i dodaje znak nowej liniiwriter.Write(tekst)– zapisuje tekst bez znaku nowej linii
Obsługa zasobów:
using()– automatycznie zamyka plik po zakończeniu bloku kodutry-catch– przechwytuje błędy podczas operacji na plikach
2. Odczyt plików – podstawowe metody
2.1 Odczyt całego pliku jednorazowo
// Metoda 1: ReadAllText() - cały plik jako string
string zawartoscPliku = File.ReadAllText("plik.txt");
Console.WriteLine(zawartoscPliku);
// Metoda 2: ReadAllLines() - każda linia jako element tablicy
string[] linie = File.ReadAllLines("plik.txt");
foreach (string linia in linie)
{
Console.WriteLine(linia);
}2.2 Odczyt pliku linia po linii (StreamReader)
using (StreamReader reader = new StreamReader("plik.txt"))
{
string linia;
while ((linia = reader.ReadLine()) != null)
{
Console.WriteLine(linia);
}
}Zapis do plików
3.1 Zapis całego tekstu jednorazowo
// Nadpisanie całego pliku
string tekst = "To jest przykładowy tekst";
File.WriteAllText("nowyPlik.txt", tekst);
// Zapis tablicy stringów (każdy jako nowa linia)
string[] linie = {"Pierwsza linia", "Druga linia", "Trzecia linia"};
File.WriteAllLines("linie.txt", linie);3.2 Dopisywanie do pliku
// Dopisanie tekstu na końcu pliku
File.AppendAllText("plik.txt", "Nowy tekst na końcu");
// Dopisanie linii
string[] noweLinie = {"Nowa linia 1", "Nowa linia 2"};
File.AppendAllLines("plik.txt", noweLinie);3.3 Zapis za pomocą StreamWriter
using (StreamWriter writer = new StreamWriter("plik.txt"))
{
writer.WriteLine("Pierwsza linia");
writer.WriteLine("Druga linia");
writer.Write("Tekst bez nowej linii");
}
// Dopisywanie za pomocą StreamWriter
using (StreamWriter writer = new StreamWriter("plik.txt", append: true))
{
writer.WriteLine("Ta linia zostanie dopisana");
}4.Obsługa błędów
Operacje na plikach mogą generować wyjątki. Zawsze należy je obsłużyć:
try
{
string zawartość = File.ReadAllText("plik.txt");
Console.WriteLine(zawartość);
}
catch (FileNotFoundException)
{
Console.WriteLine("Plik nie został znaleziony!");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Brak uprawnień do odczytu pliku!");
}
catch (IOException ex)
{
Console.WriteLine($"Błąd operacji na pliku: {ex.Message}");
}5.Sprawdzanie istnienia pliku
string nazwaPliku = "test.txt";
if (File.Exists(nazwaPliku))
{
Console.WriteLine("Plik istnieje");
string zawartość = File.ReadAllText(nazwaPliku);
Console.WriteLine(zawartość);
}
else
{
Console.WriteLine("Plik nie istnieje. Tworzę nowy...");
File.WriteAllText(nazwaPliku, "Zawartość nowego pliku");
}6.Dodatkowe informacje
Kodowanie znaków
// Odczyt z określonym kodowaniem
string tekst = File.ReadAllText("plik.txt", Encoding.UTF8);
// Zapis z określonym kodowaniem
File.WriteAllText("plik.txt", "Tekst z polskimi znakami: ąćęłńóśźż", Encoding.UTF8);Wyjaśnienie funkcji:
Encoding.UTF8– kodowanie UTF-8 (obsługuje polskie znaki)- Bez określenia kodowania system użyje domyślnego (może nie obsługiwać polskich znaków)
- UTF-8 to uniwersalne kodowanie obsługujące wszystkie języki
Ścieżki plików
// Ścieżka bezwzględna
string sciezka = @"C:\dane\plik.txt";
// Łączenie ścieżek
string sciezka2 = Path.Combine("dane", "pliki", "dokument.txt");
// Bieżący katalog
string obecnyKatalog = Directory.GetCurrentDirectory();Wyjaśnienie funkcji:
@"ścieżka"– verbatim string (nie interpretuje znaków specjalnych jak \n)Path.Combine()– inteligentnie łączy części ścieżki (dodaje / lub \ automatycznie)Directory.GetCurrentDirectory()– zwraca ścieżkę do folderu, z którego uruchomiono programPath.Combineautomatycznie dostosowuje się do systemu operacyjnego (Windows vs Linux)
7.Obsługa ciągów znaków – dzielenie tekstu
Podstawowe dzielenie po spacji
string tekst = "Ala ma kota i psa";
string[] slowa = tekst.Split(' ');
foreach (string slowo in slowa)
{
Console.WriteLine(slowo);
}
// Wynik: Ala, ma, kota, i, psaWyjaśnienie funkcji:
Split(' ')– dzieli string na części używając spacji jako separatora- Zwraca tablicę stringów
string[] - Każde słowo staje się osobnym elementem tablicy
Dzielenie po różnych separatorach
string dane = "Jan;Kowalski;30;Warszawa";
string[] elementy = dane.Split(';');
Console.WriteLine($"Imię: {elementy[0]}");
Console.WriteLine($"Nazwisko: {elementy[1]}");
Console.WriteLine($"Wiek: {elementy[2]}");
Console.WriteLine($"Miasto: {elementy[3]}");Wyjaśnienie funkcji:
Split(';')– dzieli string po średnikuelementy[0]– dostęp do pierwszego elementu tablicy (indeks 0)$"tekst {zmienna}"– interpolacja stringów (wstawianie zmiennej do tekstu)
Dzielenie po znakach nowej linii
// Przykład 1: Dzielenie po \n
string tekstWieloliniowy = "Pierwsza linia\nDruga linia\nTrzecia linia";
string[] linie = tekstWieloliniowy.Split('\n');
foreach (string linia in linie)
{
Console.WriteLine($"Linia: '{linia}'");
}
// Przykład 2: Dzielenie po \r\n (Windows)
string tekstWindows = "Linia 1\r\nLinia 2\r\nLinia 3";
string[] linieWindows = tekstWindows.Split(new string[] { "\r\n" }, StringSplitOptions.None);
// Przykład 3: Uniwersalne dzielenie po znakach nowej linii
string tekst = "A\nB\r\nC\rD";
string[] wszystkieLinie = tekst.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);Wyjaśnienie funkcji:
\n– znak nowej linii (Unix/Linux)\r\n– znak nowej linii (Windows)\r– powrót karetki (stare systemy Mac)Split('\n')– dzielenie po pojedynczym znakuSplit(new string[] { "\r\n" })– dzielenie po ciągu znakówSplit(new char[] { '\n', '\r' })– dzielenie po wielu znakach jednocześnieStringSplitOptions.None– zachowuje puste elementyStringSplitOptions.RemoveEmptyEntries– usuwa puste elementy z wyniku
Co to jest StringSplitOptions?
StringSplitOptions to enumeracja (lista stałych wartości) określająca jak funkcja Split() ma traktować puste elementy:
StringSplitOptions.None (domyślne):
string tekst = "a,,b,";
string[] wynik = tekst.Split(',');
// Wynik: ["a", "", "b", ""]
// Tablica ma 4 elementy, w tym 2 puste stringiStringSplitOptions.RemoveEmptyEntries:
string tekst = "a,,b,";
string[] wynik = tekst.Split(',', StringSplitOptions.RemoveEmptyEntries);
// Wynik: ["a", "b"]
// Tablica ma tylko 2 elementy, puste stringi zostały usuniętePraktyczny przykład – dlaczego to ważne:
// Dane z pliku CSV mogą mieć puste komórki:
string linia = "Jan,Kowalski,,Warszawa,";
// Bez RemoveEmptyEntries:
string[] dane1 = linia.Split(',');
Console.WriteLine($"Liczba elementów: {dane1.Length}"); // 5
Console.WriteLine($"Element 2: '{dane1[2]}'"); // Pusty string
Console.WriteLine($"Element 4: '{dane1[4]}'"); // Pusty string
// Z RemoveEmptyEntries:
string[] dane2 = linia.Split(',', StringSplitOptions.RemoveEmptyEntries);
Console.WriteLine($"Liczba elementów: {dane2.Length}"); // 3
Console.WriteLine($"Elementy: {string.Join(" | ", dane2)}"); // Jan | Kowalski | WarszawaKiedy używać której opcji:
- None – gdy struktura danych jest ważna (np. CSV z pustymi komórkami)
- RemoveEmptyEntries – gdy chcesz tylko wartościowe elementy (np. dzielenie tekstu na słowa)
Zaawansowane opcje dzielenia
string tekst = "apple,,banana,cherry,";
// Domyślnie - pozostawia puste elementy
string[] owoce1 = tekst.Split(',');
// Wynik: ["apple", "", "banana", "cherry", ""]
// Usuwanie pustych elementów
string[] owoce2 = tekst.Split(',', StringSplitOptions.RemoveEmptyEntries);
// Wynik: ["apple", "banana", "cherry"]
// Ograniczenie liczby elementów
string[] owoce3 = tekst.Split(',', 3);
// Wynik: ["apple", "", "banana,cherry,"]Wyjaśnienie funkcji:
Split(',')– podstawowe dzielenie, zachowuje puste elementy (domyślnie None)StringSplitOptions.RemoveEmptyEntries– usuwa puste stringi z wynikuSplit(',', 3)– ogranicza wynik do maksymalnie 3 elementów- Gdy osiągnie limit, reszta tekstu trafia do ostatniego elementu
Szczegółowe porównanie StringSplitOptions:
string przykład = "apple,,banana,cherry,";
// 1. Domyślnie (None) - zachowuje wszystkie elementy:
string[] wynik1 = przykład.Split(',');
foreach (string element in wynik1)
Console.WriteLine($"'{element}'");
// Wynik:
// 'apple'
// '' <- pusty string
// 'banana'
// 'cherry'
// '' <- pusty string na końcu
// 2. RemoveEmptyEntries - usuwa puste elementy:
string[] wynik2 = przykład.Split(',', StringSplitOptions.RemoveEmptyEntries);
foreach (string element in wynik2)
Console.WriteLine($"'{element}'");
// Wynik:
// 'apple'
// 'banana'
// 'cherry'Różnica w liczbie elementów:
wynik1.Length= 5 (z pustymi)wynik2.Length= 3 (bez pustych)
string tekstMieszany = "Ala,ma;kota:i psa";
char[] separatory = {',', ';', ':', ' '};
string[] slowa = tekstMieszany.Split(separatory, StringSplitOptions.RemoveEmptyEntries);
foreach (string slowo in slowa)
{
Console.WriteLine(slowo);
}
// Wynik: Ala, ma, kota, i, psaWyjaśnienie funkcji:
char[] separatory– tablica znaków-separatorówSplit(separatory, opcje)– dzieli po dowolnym ze znaków z tablicy- Program traktuje każdy z separatorów jako miejsce podziału
Praktyczny przykład – analiza pliku CSV
using System;
using System.IO;
class AnalizatorCSV
{
static void Main()
{
try
{
// Przykładowy plik CSV: imię,nazwisko,wiek,miasto
string sciezka = "osoby.csv";
// Utworzenie przykładowego pliku
if (!File.Exists(sciezka))
{
string[] przykładoweDane = {
"Imię,Nazwisko,Wiek,Miasto",
"Jan,Kowalski,25,Warszawa",
"Anna,Nowak,30,Kraków",
"Piotr,Wiśniewski,22,Gdańsk"
};
File.WriteAllLines(sciezka, przykładoweDane);
}
string[] linie = File.ReadAllLines(sciezka);
// Pierwsza linia to nagłówki
string[] naglowki = linie[0].Split(',');
Console.WriteLine("Nagłówki:");
for (int i = 0; i < naglowki.Length; i++)
{
Console.WriteLine($"{i}: {naglowki[i]}");
}
Console.WriteLine("\nDane:");
// Pozostałe linie to dane
for (int i = 1; i < linie.Length; i++)
{
string[] dane = linie[i].Split(',');
Console.WriteLine($"Osoba {i}:");
for (int j = 0; j < dane.Length && j < naglowki.Length; j++)
{
Console.WriteLine($" {naglowki[j]}: {dane[j]}");
}
Console.WriteLine();
}
}
catch (Exception ex)
{
Console.WriteLine($"Błąd: {ex.Message}");
}
}
}Wyjaśnienie kluczowych funkcji:
File.WriteAllLines(sciezka, tablicaStringów)– tworzy plik z tablicy (każdy element = linia)linie.Length– właściwość zwracająca liczbę elementów w tablicylinie[0]– dostęp do pierwszego elementu tablicy (nagłówki)for (int i = 1; i < linie.Length; i++)– pętla od 1 (pomijamy nagłówki)dane.Length && j < naglowki.Length– sprawdzenie obu warunków (operator AND)Exception ex– przechwytuje dowolny wyjątek i zapisuje w zmiennej exex.Message– właściwość zawierająca opis błędu
Przykład – Parser prostego formatu danych
static void PrzetworzPlikDanych(string nazwaPliku)
{
try
{
string[] linie = File.ReadAllLines(nazwaPliku);
foreach (string linia in linie)
{
// Pomijamy puste linie i komentarze
if (string.IsNullOrWhiteSpace(linia) || linia.StartsWith("#"))
continue;
// Dzielimy po dwukropku (format: klucz: wartość)
string[] czesci = linia.Split(':', 2); // Maksymalnie 2 części
if (czesci.Length == 2)
{
string klucz = czesci[0].Trim();
string wartosc = czesci[1].Trim();
Console.WriteLine($"{klucz} = {wartosc}");
}
else
{
Console.WriteLine($"Nieprawidłowy format linii: {linia}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Błąd podczas przetwarzania pliku: {ex.Message}");
}
}Wyjaśnienie kluczowych funkcji:
string.IsNullOrWhiteSpace(tekst)– sprawdza czy string jest pusty, null lub zawiera tylko białe znakilinia.StartsWith("#")– sprawdza czy string zaczyna się od podanego znaku/tekstucontinue– przechodzi do kolejnej iteracji pętli (pomija resztę kodu w pętli)Split(':', 2)– dzieli maksymalnie na 2 części (reszta trafia do drugiej części)czesci.Length == 2– sprawdza czy tablica ma dokładnie 2 elementyTrim()– usuwa białe znaki (spacje, taby) z początku i końca stringu
Obsługa różnych formatów końca linii
static string[] PodzielNaLinie(string tekst)
{
// Normalizacja różnych formatów końca linii
tekst = tekst.Replace("\r\n", "\n").Replace("\r", "\n");
// Dzielenie po znaku nowej linii
return tekst.Split('\n', StringSplitOptions.RemoveEmptyEntries);
}
// Użycie:
string zawartoscPliku = File.ReadAllText("plik.txt");
string[] linie = PodzielNaLinie(zawartoscPliku);
foreach (string linia in linie)
{
Console.WriteLine($"Linia: {linia}");
}Wyjaśnienie kluczowych funkcji:
Replace(staryTekst, nowyTekst)– zamienia wszystkie wystąpienia staryTekst na nowyTekstReplace("\r\n", "\n")– najpierw zamienia Windows na Unix (ważna kolejność!)Replace("\r", "\n")– potem zamienia Mac na Unixreturn– zwraca wartość z funkcjistatic– funkcja należy do klasy, nie do instancji obiektu- Funkcja normalizuje wszystkie formaty do \n, potem dzieli
8. Najczęstsze błędy i jak ich unikać
- Nie zamykanie plików – używaj
usingdo automatycznego zamykania - Brak obsługi wyjątków – zawsze sprawdzaj czy plik istnieje i obsługuj błędy
- Kodowanie znaków – określaj kodowanie dla polskich znaków
- Ścieżki plików – używaj
Path.Combine()zamiast konkatenacji stringów - Nieprawidłowe dzielenie – pamiętaj o
StringSplitOptions.RemoveEmptyEntries - Różne formaty końca linii – normalizuj przed przetwarzaniem (\r\n vs \n vs \r)
- Puste linie i białe znaki – używaj
Trim()i sprawdzajIsNullOrWhiteSpace()
9.Przykład praktyczny – Program do notatek
using System;
using System.IO;
class ProgramNotatek
{
static string nazwaPliku = "notatki.txt";
static void Main()
{
while (true)
{
Console.Clear();
Console.WriteLine("=== PROGRAM DO NOTATEK ===");
Console.WriteLine("1. Wyświetl notatki");
Console.WriteLine("2. Dodaj notatkę");
Console.WriteLine("3. Wyczyść wszystkie notatki");
Console.WriteLine("0. Wyjście");
Console.Write("Wybierz opcję: ");
string wybor = Console.ReadLine();
switch (wybor)
{
case "1":
WyswietlNotatki();
break;
case "2":
DodajNotatke();
break;
case "3":
WyczyscNotatki();
break;
case "0":
return;
default:
Console.WriteLine("Nieprawidłowa opcja!");
break;
}
Console.WriteLine("Naciśnij dowolny klawisz...");
Console.ReadKey();
}
}
static void WyswietlNotatki()
{
Console.Clear();
Console.WriteLine("=== TWOJE NOTATKI ===");
if (File.Exists(nazwaPliku))
{
string[] notatki = File.ReadAllLines(nazwaPliku);
if (notatki.Length == 0)
{
Console.WriteLine("Brak notatek.");
}
else
{
for (int i = 0; i < notatki.Length; i++)
{
Console.WriteLine($"{i + 1}. {notatki[i]}");
}
}
}
else
{
Console.WriteLine("Brak pliku z notatkami.");
}
}
static void DodajNotatke()
{
Console.Clear();
Console.WriteLine("=== DODAJ NOTATKĘ ===");
Console.Write("Wpisz swoją notatkę: ");
string notatka = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(notatka))
{
string notatkaCzas = $"[{DateTime.Now:yyyy-MM-dd HH:mm}] {notatka}";
File.AppendAllText(nazwaPliku, notatkaCzas + Environment.NewLine);
Console.WriteLine("Notatka została dodana!");
}
else
{
Console.WriteLine("Notatka nie może być pusta!");
}
}
static void WyczyscNotatki()
{
Console.Clear();
Console.WriteLine("=== WYCZYŚĆ NOTATKI ===");
Console.Write("Czy na pewno chcesz usunąć wszystkie notatki? (t/n): ");
string potwierdzenie = Console.ReadLine().ToLower();
if (potwierdzenie == "t" || potwierdzenie == "tak")
{
File.WriteAllText(nazwaPliku, "");
Console.WriteLine("Wszystkie notatki zostały usunięte!");
}
else
{
Console.WriteLine("Operacja anulowana.");
}
}
}10. Zadania do wykonania
Zadanie 1 – Podstawowe operacje
Napisz program, który:
- Sprawdza czy plik „dane.txt” istnieje
- Jeśli nie – tworzy go z przykładowymi danymi
- Odczytuje zawartość i wyświetla ją
- Dopisuje aktualną datę i godzinę
Zadanie 2 – Licznik słów
Napisz program, który:
- Odczytuje plik tekstowy
- Liczy ilość linii, słów i znaków
- Zapisuje statystyki do nowego pliku „statystyki.txt”
Zadanie 3 – Książka adresowa
Napisz prostą książkę adresową, która:
- Przechowuje dane w pliku „kontakty.txt”
- Umożliwia dodawanie kontaktów (imię, nazwisko, telefon)
- Wyświetla wszystkie kontakty
- Wyszukuje kontakt po nazwisku
Zadanie 5 – Analizator tekstu
Napisz program, który:
- Odczytuje plik tekstowy
- Dzieli tekst na słowa (po spacjach)
- Liczy wystąpienia każdego słowa
- Zapisuje wyniki do pliku „analiza.txt”
Zadanie 6 – Parser pliku konfiguracyjnego
Utwórz program, który:
- Odczytuje plik „config.txt” w formacie „klucz=wartość” (jedna para na linię)
- Dzieli każdą linię po znaku „=”
- Przechowuje konfigurację w Dictionary<string, string>
- Umożliwia wyszukiwanie wartości po kluczu
Zadanie 7 – Konwerter formatów końca linii
Napisz program, który:
- Odczytuje plik tekstowy
- Wykrywa format końca linii (\n, \r\n, \r)
- Konwertuje na wybrany format
- Zapisuje do nowego pliku
Zadanie 8 – Prosty parser CSV
Utwórz program do analizy pliku CSV, który:
- Wczytuje plik z danymi oddzielonymi przecinkami
- Dzieli każdą linię na kolumny
- Wyświetla dane w formie tabeli
- Umożliwia wyszukiwanie po wybranej kolumnie