• C# i .NET
  • Jak poprawnie wczytać plik CSV w C#

Jak poprawnie wczytać plik CSV w C#

Radosław Krajewski • 3 listopada 2026
Ilustracja pokazuje różne sposoby przetwarzania danych z pliku CSV w C#. Widoczne są ikony reprezentujące arkusze kalkulacyjne i operacje na nich.

Spis treści

Eksport zamówień wygląda poprawnie, dopóki przecinek w nazwie produktu nie przesunie kwoty do sąsiedniej kolumny. Plik CSV to tekstowa tabela, którą łatwo przenieść między programami, ale sposób zapisu pól zależy od separatora, cudzysłowów i kodowania.

Ilustracja pokazuje różne sposoby przetwarzania danych z pliku CSV w C#. Widzimy ikony reprezentujące tabele i arkusze, sugerujące analizę i parsowanie danych.

Poprawny import zaczyna się od rozpoznania reguł pliku

  • Rozdzielaj rekordy zgodnie z formatem, nie przez proste cięcie tekstu.
  • Ustaw separator i kulturę jawnie, bo domyślne wartości mogą zależeć od środowiska.
  • Przetwarzaj wiersze strumieniowo, gdy plik jest duży.
  • Sprawdź wynik przed użyciem, szczególnie typy danych i wartości pochodzące od użytkowników.

Przecinek i koniec wiersza nie zawsze kończą pole

Wartość „Kawa, mielona” może być jednym polem, choć zawiera przecinek. Podobnie tekst w cudzysłowie może mieć znak nowej linii, więc rozcięcie pliku po każdym przecinku albo końcu wiersza potrafi rozsypać całą tabelę.

RFC 4180 opisuje popularne reguły CSV: pola mogą być ujęte w podwójne cudzysłowy, a cudzysłów wewnątrz takiego pola zapisuje się podwójnie. Dokument dopuszcza też znaki nowej linii wewnątrz pola. Nie jest jednak uniwersalnym kontraktem dla wszystkich eksportów, dlatego najpierw sprawdź próbkę pliku i wymagania systemu, który go wygenerował.

Przykładowy wiersz 42;"Kawa, mielona";"Klient napisał: ""proszę o kontakt""" ma trzy pola, mimo że zawiera kilka separatorów i cudzysłowów. Metoda Split nie zna tych reguł; parser CSV je rozpoznaje.

CsvHelper mapuje rekordy, a TextFieldParser wystarcza do prostych plików

Wybór narzędzia zależy od tego, co ma się stać z danymi po imporcie. CsvHelper ułatwia mapowanie kolumn na obiekty C#, obsługę nagłówków i iteracyjne pobieranie rekordów. TextFieldParser z przestrzeni nazw Microsoft.VisualBasic.FileIO potrafi czytać pola rozdzielane oraz pliki o stałej szerokości i może wystarczyć w małym narzędziu bez rozbudowanego mapowania.

Przy typowanym imporcie wybrałbym CsvHelper: mapowanie na klasę czytelniej ujawnia, które kolumny program wykorzystuje. Gdy potrzebne jest jednorazowe odczytanie prostego pliku, TextFieldParser może ograniczyć liczbę zależności. W obu przypadkach jawnie ustaw separator, zamiast zakładać, że każdy plik używa przecinka.

W polskich eksportach spotyka się średnik, między innymi wtedy, gdy przecinek pełni rolę separatora dziesiętnego. Samo rozszerzenie .csv nie rozstrzyga, jakiego separatora użyto. Przy imporcie kwot uwzględnij też kulturę, według której parser zamienia tekst na typ liczbowy.

Duży plik czytaj rekord po rekordzie

Przy eksporcie zawierającym wiele zamówień kuszące jest wczytanie całej zawartości przez File.ReadAllText, a potem podzielenie jej na wiersze. Taki kod trzyma pełny tekst w pamięci i nadal nie radzi sobie z nową linią wewnątrz cytowanego pola. Parser strumieniowy oddaje rekordy pojedynczo, więc program może przetwarzać dane bez budowania kopii całego pliku.

Poniższy przykład zakłada nagłówki Id, Produkt i Cena, średnik jako separator oraz polską kulturę dla konwersji wartości. W produkcji trzeba dopasować te ustawienia do umowy z dostawcą pliku.

using System.Globalization;
using System.Text;
using CsvHelper;
using CsvHelper.Configuration;

public sealed class ImportRow
{
    public string Id { get; set; } = "";
    public string Produkt { get; set; } = "";
    public decimal Cena { get; set; }
}

var config = new CsvConfiguration(CultureInfo.GetCultureInfo("pl-PL"))
{
    Delimiter = ";",
    HasHeaderRecord = true
};

using var reader = new StreamReader(
    "zamowienia.csv",
    new UTF8Encoding(false),
    detectEncodingFromByteOrderMarks: true);

using var csv = new CsvReader(reader, config);

foreach (var row in csv.GetRecords())
{
    Console.WriteLine($"{row.Id}: {row.Produkt}, {row.Cena}");
}

Identyfikator jest tekstem celowo: numer typu 00042 po konwersji na liczbę straciłby zera wiodące. Jeśli chcesz zachować tylko część rekordów, od razu filtruj je w pętli. Nie zamieniaj wyniku na listę bez potrzeby, bo wtedy ponownie zgromadzisz wszystkie obiekty w pamięci.

Objaw importu podpowiada, gdzie szukać błędu

Niepoprawny wynik często wygląda jak błąd w danych, choć przyczyna leży w ustawieniach importu. Zanim zaczniesz zmieniać plik, porównaj objaw z konfiguracją parsera.

Sytuacja lub objaw Prawdopodobna przyczyna Co zrobić
Cały wiersz trafia do jednej kolumny Ustawiony separator nie pasuje do pliku Sprawdź kilka wierszy i ustaw jawnie przecinek, średnik albo tabulator
Polskie znaki są nieczytelne Parser używa innego kodowania niż plik Ustal kodowanie źródła; dla UTF-8 sprawdź też, czy plik zawiera BOM
Opis produktu pojawia się w kilku wierszach Parser potraktował nową linię wewnątrz cytowanego pola jak koniec rekordu Użyj parsera CSV, który obsługuje cytowane pola wielowierszowe
Kwota z przecinkiem nie daje się zamienić na liczbę Kultura konwersji nie pasuje do zapisu liczby Ustaw kulturę zgodną ze źródłem albo wczytaj wartość jako tekst i zweryfikuj ją przed konwersją
Identyfikator stracił zera na początku Parser zamienił go na typ liczbowy Przechowuj identyfikator jako tekst

Najpierw porównaj separator, kodowanie i ustawienia kultury z pierwszymi kilkoma rekordami. Nie naprawiaj przecinków w danych ręcznie, dopóki nie sprawdzisz, czy cytowanie pól jest zgodne z regułami eksportu.

Eksport CSV może uruchomić formułę w arkuszu

Gdy tworzysz plik do pobrania, zagrożeniem może być wartość pochodząca od użytkownika, która zaczyna się od znaku interpretowanego przez arkusz jako formuła. Po otwarciu takiego eksportu w programie arkuszowym zawartość komórki może zostać wykonana, a samo ujęcie tekstu w cudzysłowy CSV nie daje pewnej ochrony.

OWASP opisuje ten problem jako CSV Injection. Zabezpieczenie zależy od tego, kto otworzy plik: metoda przeznaczona do wyświetlania danych w Excelu może zmienić wartość, którą później odczyta inny system. Rozdziel więc eksport dla człowieka od pliku wymiany maszynowej, a dla obu określ i przetestuj zasady oczyszczania danych.

Przy imporcie z niezaufanego źródła traktuj komórki jako dane, nie jako polecenia. Przy eksporcie nie obiecuj uniwersalnego zabezpieczenia bez sprawdzenia zachowania docelowego programu, bo arkusze mogą różnie interpretować wartości.

Ustal kontrakt pliku, zanim podłączysz import

Najmniej kłopotów daje krótki kontrakt: separator, kodowanie, obecność nagłówka, format liczb oraz reguły cytowania. Wtedy można ustawić parser raz i odrzucić plik, który nie spełnia oczekiwań, zamiast po cichu importować przesunięte kolumny.

Moim zdaniem to lepsza granica odpowiedzialności niż „sprytne” zgadywanie formatu przy każdym uruchomieniu. Jeśli kontrakt jest jawny, plik CSV pozostaje prostym tekstem do wymiany danych, a błędy wychodzą przy wejściu, zanim trafią do dalszej części aplikacji.

FAQ - Najczęstsze pytania

Nie rozpoznaje reguł cytowania. Przecinek może być częścią pola, a cudzysłów wewnątrz pola zapisuje się jako dwa cudzysłowy. Cytowane pole może też zawierać znak nowej linii, dlatego do podziału rekordów użyj parsera CSV.

Sprawdź próbkę pliku i wymagania systemu, który go wygenerował, bo rozszerzenie CSV nie określa separatora. W polskich eksportach często spotyka się średnik, a przy konwersji kwot ustaw kulturę zgodną z zapisem liczb, na przykład pl-PL.

CsvHelper sprawdzi się przy mapowaniu nagłówków i rekordów na obiekty C#. TextFieldParser może wystarczyć do odczytu prostego pliku w małym narzędziu, gdy nie potrzebujesz rozbudowanego mapowania. W obu przypadkach jawnie ustaw separator.

Przetwarzaj rekordy strumieniowo, na przykład w pętli po csv.GetRecords<ImportRow>(), i nie zamieniaj ich na listę bez potrzeby. Przechowuj identyfikator jako tekst, aby wartość 00042 nie straciła zer wiodących podczas konwersji na liczbę.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi

csv
kodowanie znaków
strumieniowanie
csvhelper
Autor Radosław Krajewski
Radosław Krajewski
Nazywam się Radosław Krajewski i od 6 lat zgłębiam tajniki programowania .NET, chmury Azure oraz sztucznej inteligencji. Moja przygoda z tymi technologiami zaczęła się od fascynacji tym, jak złożone problemy można rozwiązywać za pomocą kodu i innowacyjnych narzędzi. Staram się przekazywać tę wiedzę w sposób zrozumiały, dzieląc się swoimi doświadczeniami i spostrzeżeniami na kursdotnet.pl. W moich artykułach skupiam się na praktycznych aspektach, porównuję różne rozwiązania i analizuję najnowsze trendy, aby dostarczyć Wam rzetelne i aktualne informacje, które pomogą Wam rozwijać się w tej dynamicznie zmieniającej się dziedzinie.

Udostępnij artykuł

Napisz komentarz