CodeGym /Kursy /C# SELF /Praca z plikami w C#: klasa

Praca z plikami w C#: klasa System.IO.File

C# SELF
Poziom 35, Lekcja 2
Dostępny

1. Klasa File: prostota i skuteczność

Klasa System.IO.File wciela w życie koncepcję "jedna operacja — jedna metoda". Udostępnia zestaw wysokopoziomowych metod, które kapsułkują złożoną logikę komunikacji z systemem operacyjnym, pozwalając Ci skupić się na swoim zadaniu.

Co konkretnie potrafi klasa File?

  • Zapisywanie tekstu lub dowolnych danych binarnych do pliku.
  • Czytanie zawartości pliku w całości lub linia po linii (dla plików tekstowych).
  • Kopiowanie, usuwanie i zmiana nazwy istniejących plików.
  • Sprawdzanie istnienia pliku pod podaną ścieżką.
  • Praca z atrybutami pliku (np. data utworzenia, rozmiar – przez powiązane klasy).

Dlaczego to wygodne?
Wszystkie metody File są statyczne. To znaczy, że nie musisz tworzyć obiektu, żeby ich użyć – wystarczy po prostu wywołać odpowiednią metodę, np.:

File.WriteAllText("data.txt", "Cześć, pliku!");

Taki styl bardzo przypomina pracę z klasą Console: wszystko jest proste i jasne, nic nie trzeba „tworzyć”, nie trzeba martwić się o strumienie.

Jeśli chcesz mieć pod ręką uniwersalne i wygodne narzędzie do pracy z plikami, klasa System.IO.File jest właśnie taka. Oferuje mnóstwo metod do czytania, zapisywania, kopiowania, usuwania i innych podstawowych operacji na plikach, jak szwajcarski scyzoryk dla systemu plików.

Jak używać File?

Wszystkie metody klasy File są statyczne, więc nie musisz tworzyć obiektu, żeby ich użyć — po prostu piszesz File.CośTam(). To bardzo podobne do wywołań typu Console.WriteLine(), tylko teraz dla plików.

string text = "Cześć, świecie!";
string path = "hello.txt";

// Zapis tekstu do pliku (jeśli plik istniał, zostanie nadpisany!)
File.WriteAllText(path, text);

// Odczyt całego pliku do jednego stringa
string readText = File.ReadAllText(path);

Console.WriteLine("Zawartość pliku: " + readText);

Lifehack: Jeśli 10 razy z rzędu piszesz ReadAllText i WriteAllText, to pewnie robisz coś nie tak — te metody są dobre do prostych scenariuszy i małych plików.

Najczęściej używane metody klasy File

Metoda Opis
File.Exists(path)
Sprawdza, czy plik istnieje pod podaną ścieżką
File.Delete(path)
Usuwa plik
File.Copy(src, dest)
Kopiuje plik
File.Move(src, dest)
Przenosi (lub zmienia nazwę) plik
File.ReadAllText(path)
Czyta cały plik jako jeden string
File.WriteAllText(path, t)
Zapisuje string do pliku (nadpisuje, jeśli istnieje)
File.ReadAllLines(path)
Czyta wszystkie linie pliku do tablicy stringów
File.WriteAllLines(path, a)
Zapisuje tablicę stringów do pliku (każda linia osobno)
File.ReadAllBytes(path)
Czyta wszystkie bajty pliku do tablicy bajtów
File.WriteAllBytes(path, b)
Zapisuje tablicę bajtów do pliku

2. Zapisywanie i czytanie plików tekstowych

Zobaczmy najczęściej używane metody klasy File na praktycznych przykładach. Do pracy z nimi zawsze potrzebna jest dyrektywa using System.IO; na początku Twojego pliku z kodem.

using System;
using System.IO; // Konieczne do pracy z klasą File
using System.Linq; // Może się przydać do ReadLines

Czytanie pliku w całości

To jedno z najczęstszych zadań: zapisanie danych tekstowych do pliku lub ich ponowne wczytanie.

Zapisywanie jednej linii (lub całego tekstu) do pliku: File.WriteAllText() Ta metoda tworzy nowy plik pod podaną ścieżką. Jeśli plik o tej nazwie już istnieje, jego zawartość zostanie całkowicie nadpisana nowym tekstem.

string myFilePath = "notes.txt";
string textContent = "Dzisiaj jest świetny dzień na naukę C#.\nNie zapomnij zrobić zadania domowego.";

// Zapisujemy cały tekst do pliku
File.WriteAllText(myFilePath, textContent); 
Console.WriteLine($"Plik '{myFilePath}' został pomyślnie utworzony/nadpisany.");

Czytanie pliku w całości do jednego stringa: File.ReadAllText() Ta metoda czyta całą zawartość pliku tekstowego od początku do końca i zwraca ją jako jeden duży string.

string myFilePath = "notes.txt";

// Ważne: zawsze sprawdzaj istnienie pliku przed czytaniem, żeby uniknąć błędów!
if (File.Exists(myFilePath))
{
    string content = File.ReadAllText(myFilePath);
    Console.WriteLine($"Zawartość pliku '{myFilePath}':\n{content}");
}
else
{
    Console.WriteLine($"Błąd: Plik '{myFilePath}' nie został znaleziony do odczytu.");
}

Ważna uwaga: Metody File.WriteAllText() i File.ReadAllText() świetnie nadają się do małych i średnich plików (np. notatki tekstowe, pliki konfiguracyjne, krótkie logi). Ale jeśli plik jest bardzo duży (powiedzmy, dziesiątki lub setki megabajtów, a tym bardziej gigabajty), wczytanie całej jego zawartości do jednego stringa w pamięci RAM może zużyć ogromną ilość pamięci i nawet doprowadzić do OutOfMemoryException. W takich przypadkach są bardziej wydajne podejścia (o nich niżej i w kolejnych wykładach).

3. Czytanie pliku linia po linii

Gdy pracujesz z dużymi plikami tekstowymi, takimi jak obszerne raporty CSV, długie pliki logów czy tekstowe bazy danych, czytanie ich w całości do pamięci (czyli wczytanie całego pliku naraz z dysku do RAM-u komputera) staje się nieefektywne lub wręcz niemożliwe. Tu z pomocą przychodzi File.ReadLines().

File.ReadLines(path) — ta metoda nie ładuje całego pliku naraz. Zamiast tego zwraca specjalną kolekcję (IEnumerable<string>), która pozwala czytać plik linia po linii w miarę potrzeby. To się nazywa "leniwe czytanie" albo "czytanie strumieniowe".

string logFilePath = "application.log";
string text = """
INFO: Aplikacja uruchomiona.
WARN: Wykryto problem w module X.
ERROR: Krytyczny błąd sieci.
INFO: Aplikacja zatrzymana.    
""";    
    
// Załóżmy, że mamy plik logu z wieloma liniami.
File.WriteAllText(logFilePath, text);

// Każda iteracja pętli czyta i przetwarza tylko JEDNĄ linię 
foreach (string line in File.ReadLines(logFilePath))
    Console.WriteLine($"- {line}");

Zaleta: Plik jest czytany i przetwarzany po kawałku, co znacznie zmniejsza zużycie pamięci przy pracy z dużymi plikami.

Różnica między File.ReadLines() a File.ReadAllLines():

  • File.ReadAllLines(path): Ładuje wszystkie linie pliku naraz do pamięci RAM i zwraca je jako tablicę string[]. Po wywołaniu tej metody cały plik jest już w pamięci. To wygodne, jeśli potrzebujesz pełnego dostępu do wszystkich linii jednocześnie (np. do sortowania lub wyszukiwania).
  • File.ReadLines(path): Zwraca IEnumerable<string>, który pozwala przechodzić przez linie pliku po jednej. Plik jest czytany tylko wtedy, gdy żądasz kolejnej linii (np. w pętli foreach). To bardziej wydajne dla bardzo dużych plików, gdy nie możesz lub nie chcesz ładować wszystkiego do pamięci.

4. Zapisywanie tablicy stringów

Czasem masz listę stringów (np. z jakiejś kolekcji) i chcesz zapisać każdą linię jako osobny wpis w pliku.

File.WriteAllLines(path, contents)

Ta metoda przyjmuje tablicę stringów (lub dowolną kolekcję stringów) i zapisuje każdą linię jako osobną linię w pliku. Tak jak WriteAllText(), nadpisuje plik, jeśli już istnieje.

string membersFilePath = "participants.txt";
string[] names = { "Jan Kowalski", "Maria Sidorowa", "Sergiusz Kozłow", "Anna Kuzniecowa" };

// Każda linia tablicy zostanie zapisana jako osobna linia w pliku
File.WriteAllLines(membersFilePath, names);

// Sprawdzamy zawartość
Console.WriteLine($"Zawartość '{membersFilePath}':");
foreach (string line in File.ReadLines(membersFilePath))
    Console.WriteLine($"- {line}");

5. Praca z plikami binarnymi

Klasa File nie ogranicza się tylko do danych tekstowych. Udostępnia też wygodne metody do czytania i zapisywania danych binarnych, czyli surowych bajtów. To przydatne np. przy zapisywaniu ustawień w specyficznym formacie, pracy z obrazami, audio lub dowolnymi danymi, które nie są zwykłym tekstem.

Zapisywanie tablicy bajtów: File.WriteAllBytes() Tworzy lub nadpisuje plik, zapisując do niego całą przekazaną tablicę bajtów.

string binaryDataPath = "settings.bin";
// Przykład danych binarnych: obrazek, wideo, dowolne dane
byte[] settingsData = { 0x01, 0x0A, 0x2C, 0xFF, 0x00, 0x3F }; // Jakieś "surowe" ustawienia

File.WriteAllBytes(binaryDataPath, settingsData);
Console.WriteLine($"Dane binarne zapisane do pliku '{binaryDataPath}'.");

Ważne: jeśli spróbujesz otworzyć 'settings.bin' w Notatniku, zobaczysz nieczytelne znaki ("krzaki"), bo te bajty nie są przeznaczone do interpretacji jako tekst.

Czytanie tablicy bajtów: File.ReadAllBytes() Czyta całą zawartość pliku binarnego do tablicy bajtów.

if (File.Exists(binaryDataPath))
{
    byte[] readSettings = File.ReadAllBytes(binaryDataPath);
    Console.WriteLine($"Przeczytano {readSettings.Length} bajtów z pliku '{binaryDataPath}'.");

    Console.Write("Przeczytane bajty (w formacie szesnastkowym): ");
    foreach (byte b in readSettings)
        Console.Write($"{b:X2} "); // :X2 formatuje bajt jako dwucyfrową liczbę szesnastkową

    Console.WriteLine();
}
else
{
    Console.WriteLine($"Błąd: Plik binarny '{binaryDataPath}' nie został znaleziony.");
}

Te metody są bardzo wygodne do zapisywania małych porcji danych binarnych, np. do cache'owania małych obrazków lub serializowanych obiektów.

6. Zarządzanie plikami na dysku

Klasa File udostępnia też metody do zarządzania plikami na dysku.

Sprawdzanie istnienia pliku: File.Exists(path) Zwraca true, jeśli plik pod podaną ścieżką istnieje, i false w przeciwnym razie. Bardzo polecam używać tej metody przed próbą czytania, usuwania lub innych operacji, które mogą wywołać błąd, jeśli plik nie zostanie znaleziony.

string myFileToCheck = "report.txt";
// Najpierw go utworzymy dla przykładu
File.WriteAllText(myFileToCheck, "Próbny raport.");

if (File.Exists(myFileToCheck))
{
    Console.WriteLine($"Plik '{myFileToCheck}' znaleziony.");
    // Teraz można bezpiecznie czytać, usuwać itd.
}
else
{
    Console.WriteLine($"Plik '{myFileToCheck}' nie został znaleziony.");
}

Usuwanie pliku: File.Delete(path) bezpowrotnie usuwa plik z dysku. Bądź bardzo ostrożny, ta operacja jest nieodwracalna!

string fileToDelete = "temp_file.txt";
// Tworzymy plik dla przykładu
File.WriteAllText(fileToDelete, "Ten plik zostanie usunięty.");

if (File.Exists(fileToDelete))
{
    File.Delete(fileToDelete);
    Console.WriteLine($"Plik '{fileToDelete}' został pomyślnie usunięty.");
}
else
{
    Console.WriteLine($"Plik '{fileToDelete}' już nie istnieje lub został wcześniej usunięty.");
}

7. Wyjątki przy pracy z plikami

Praca z plikami zawsze wiąże się z komunikacją z systemem operacyjnym i zasobami zewnętrznymi, co czyni ją potencjalnie podatną na błędy.

Obsługuj wyjątki (błędy)! Przy pracy z plikami może się zdarzyć mnóstwo problemów, które Twój program powinien umieć poprawnie obsłużyć, a nie po prostu "wywalić się". Typowe sytuacje:

  • FileNotFoundException: Plik, do którego próbujesz się dostać, nie istnieje pod podaną ścieżką.
  • DirectoryNotFoundException: Folder, w którym próbujesz utworzyć lub znaleźć plik, nie istnieje.
  • UnauthorizedAccessException: Twój program nie ma uprawnień do czytania, zapisywania lub usuwania pliku/folderu (np. plik jest systemowy lub znajduje się w chronionym miejscu).
  • IOException: Ogólny błąd wejścia/wyjścia, często pojawia się, gdy plik jest już otwarty przez inny program i zablokowany, albo nie ma miejsca na dysku.

Zawsze używaj konstrukcji try-catch do bezpiecznej pracy z plikami. To pozwala "złapać" błąd i podjąć odpowiednie działania (np. wyświetlić komunikat użytkownikowi, zapisać do logu).

string sensitiveFilePath = "secret_data.txt";

try
{
    // Próba odczytu pliku, który może nie istnieć lub do którego nie ma dostępu
    string secretContent = File.ReadAllText(sensitiveFilePath);
    Console.WriteLine($"Przeczytano tajną zawartość: {secretContent}");
}
catch (FileNotFoundException) // Konkretny błąd: plik nie znaleziony
{
    Console.WriteLine($"Błąd: Plik '{sensitiveFilePath}' nie został znaleziony. Upewnij się, że ścieżka jest poprawna.");
}
catch (IOException ex) // Ogólny błąd wejścia/wyjścia (np. plik zablokowany)
{
    Console.WriteLine($"Błąd wejścia/wyjścia przy pracy z plikiem '{sensitiveFilePath}': {ex.Message}");
}
catch (Exception ex) // Każdy inny nieoczekiwany błąd
{
    Console.WriteLine($"Wystąpił nieoczekiwany błąd: {ex.Message}");
}

8. Kiedy System.IO.File — to nie najlepszy wybór?

Chociaż klasa File jest niesamowicie uniwersalna, są scenariusze, gdzie jej użycie nie jest optymalne albo wręcz możliwe:

Ogromne pliki (dziesiątki+ gigabajtów): Metody ReadAllText()/ReadAllBytes() ładują cały plik do pamięci. Dla ekstremalnie dużych plików to skończy się OutOfMemoryException. W takich przypadkach File.ReadLines() (dla tekstu) albo bezpośrednie podejście ze strumieniami (Streams) (FileStream) jest konieczne do czytania/zapisywania po kawałku. (O strumieniach będzie w kolejnym wykładzie).

Precyzyjna kontrola dostępu do pliku: Jeśli potrzebujesz bardzo specyficznej kontroli nad tym, jak plik jest otwierany (np. wyłączny dostęp, żeby nikt inny nie mógł go czytać ani zapisywać, albo przeciwnie, jednoczesny dostęp do czytania przez kilka procesów), File nie daje takiej szczegółowości. Do tego potrzebne są niższopoziomowe klasy strumieni.

Operacje asynchroniczne: Dla aplikacji wysokowydajnościowych, które muszą wykonywać operacje na plikach bez blokowania głównego wątku (np. w serwerach webowych), potrzebne są metody asynchroniczne (np. ReadAllTextAsync()), które też bazują na strumieniach.

Czytanie/zapisywanie fragmentów: Jeśli musisz przeczytać tylko część pliku od określonego offsetu albo zapisać dane w środku istniejącego pliku, metody File się nie nadają. Do tego potrzebny jest bezpośredni dostęp do strumienia pliku.

9. Podsumowanie

Dlaczego System.IO.File jest tak popularny?

  • Prostota i zwięzłość: Jego statyczne metody pozwalają wykonać złożone operacje dosłownie w jednej-dwóch linijkach kodu. To bardzo upraszcza programowanie i sprawia, że kod jest czytelniejszy.
  • Automatyczne zarządzanie zasobami: File sam dba o otwieranie i zamykanie plików przy każdej operacji, zwalniając Cię z konieczności ręcznego zarządzania zasobami (co często prowadzi do błędów).
  • Uniwersalność do codziennych zadań: Do większości typowych operacji na plikach tekstowych i binarnych małych i średnich rozmiarów możliwości File są w zupełności wystarczające.
  • Powszechność: W większości praktycznych zadań i na rozmowach kwalifikacyjnych oczekuje się, że umiesz pewnie korzystać z metod klasy File.
  • Ukryta złożoność: Wszystkie metody są napisane tak, żeby kapsułkować złożoną logikę komunikacji z systemem operacyjnym, dając Ci czysty i zrozumiały interfejs.

Częste pytania i doprecyzowania

Jaki jest maksymalny rozmiar pliku dozwolony dla File.ReadAllText()?
Dla plików do kilkudziesięciu megabajtów (< 100 MB) zwykle nie ma żadnych problemów. Jeśli plik jest większy (setki MB, gigabajty), użycie File.ReadAllText() może doprowadzić do wyczerpania pamięci RAM (wyjątek OutOfMemoryException). W takich przypadkach należy użyć File.ReadLines() do czytania linia po linii lub bardziej zaawansowanych narzędzi do przetwarzania strumieniowego, o których dowiesz się później.

Czy można utworzyć/zapisać plik w folderze, którego nie ma?
Nie. Metody File.WriteAllText(), File.WriteAllBytes() i inne nie tworzą automatycznie katalogów nadrzędnych. Jeśli podasz ścieżkę C:\MyAppData\Logs\current.log, a foldery MyAppData lub Logs nie istnieją, pojawi się DirectoryNotFoundException. Najpierw musisz się upewnić, że wszystkie potrzebne foldery istnieją, używając metod klasy System.IO.Directory (np. Directory.CreateDirectory("myfolder")).

Jak sprawdzić datę utworzenia, modyfikacji lub rozmiar pliku?
Aby uzyskać bardziej szczegółowe informacje o pliku, takie jak data utworzenia, data ostatniej modyfikacji, rozmiar, atrybuty (ukryty, tylko do odczytu itd.), użyj klasy System.IO.FileInfo. Udostępnia ona wiele przydatnych właściwości.

2
Zadanie
C# SELF, poziom 35, lekcja 2
Niedostępne
Tworzenie i zapisywanie danych do pliku
Tworzenie i zapisywanie danych do pliku
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION