Grafikus C# – Windows Forms tanfolyam › 10.5 lecke Windows Forms · 10.5 lecke · CSV A CSV (comma-separated values) a legegyszerűbb táblázatos formátum: minden sor egy rekord, az értékeket elválasztó jel tagolja. Az Excel, az iskolai nyilvántartók és a legtöbb program képes beolvasni és írni. A 8.9 leckében egyszerű CSV-t mentettél és töltöttél be CSV fájl kezelése
Split módszerrel, ami addig működik, amíg a mezőkben nincs pontosvessző, idézőjel vagy sortörés. A valódi CSV-fájlok ennél sokkal alattomosabbak. Ebben a leckében megírod a helyes feldolgozót (idézőjelek, többsoros mezők), felismered az elválasztót és a fejlécet, számokat olvasol a magyar tizedesvesszővel, és biztonságosan exportálsz (Excel-barát kódolás, képletbeszúrás elleni védelem).
A lecke végére
- ismered a CSV szabályait (idézőjel, dupla idézőjel, többsoros mező), és tudod, miért nem elég a
Split; - állapotgépes feldolgozót írsz karakterenként (
StreamReader.Read,Peek); - felismered az elválasztót (
;,,, tabulátor), és megfelelteted az oszlopokat a fejléc szerint; - számot és dátumot olvasol a megfelelő kultúrával (tizedesvessző vagy tizedespont);
- helyesen írsz CSV-t (idézés,
UTF-8BOM az Excelnek), és védekezel a képletbeszúrás ellen; - a hibás sorokat jelented, a jókat betöltöd (nincs adatvesztés, nincs összeomlás).
Mielőtt elkezded
Ehhez a leckéhez a 8.9 lecke (egyszerű CSV), a 10.2–10.3 lecke (StreamReader, kódolás) és a 8.8 lecke (adatellenőrzés) szükséges.
A CSV szabályai és a Split csapdái
Név;Ár;Megjegyzés
Toll;120,5;kék
"Füzet; A4";350;"Vonalas, 60 lapos"
"Marker ""XL""";480;"első sor
második sor";), az idézőjelet vagy a sortörést, " idézőjelek közé kerül: "Füzet; A4". Az idézőjelen belül az elválasztó nem választ el."Marker ""XL""" jelentése: Marker "XL".ReadLine alapú feldolgozás itt hibás.| is gyakori. A feldolgozónak az elválasztót paraméterként kell kapnia (vagy felismernie).Jósolj!
A sor.Split(';') hívás a fenti "Füzet; A4";350;"Vonalas, 60 lapos" sorra hány mezőt ad, és mi az első?
Jóslat ellenőrzése
4 mezőt (a helyes 3 helyett): az első "Füzet (az idézőjellel), a második A4". A Split nem ismeri az idézőjeles mezőt: a feldolgozót karakterenként kell írni.
Állapotgépes feldolgozó
A feldolgozó karakterenként olvas, és egy jelzőt (idezoben) tart: az idézőjelben a karakterek a mező részei, azon kívül az elválasztó mezőt, a sortörés rekordot zár:
public static class Csv
{
public static List<string[]> Elemez(TextReader olvaso, char elvalaszto)
{
List<string[]> sorok = new List<string[]>();
List<string> mezok = new List<string>();
StringBuilder mezo = new StringBuilder();
bool idezoben = false;
bool vanTartalom = false; // a nem üres sor jelzése (az üres sorokat kihagyjuk)
int c;
while ((c = olvaso.Read()) != -1)
{
char ch = (char)c;
if (idezoben)
{
if (ch == '"')
{
if (olvaso.Peek() == '"') { mezo.Append('"'); olvaso.Read(); } // "" → egy idézőjel
else { idezoben = false; } // az idézett rész vége
}
else { mezo.Append(ch); } // az idézőjelben minden karakter a mező része (sortörés is)
}
else if (ch == '"') { idezoben = true; vanTartalom = true; }
else if (ch == elvalaszto) { mezok.Add(mezo.ToString()); mezo.Clear(); vanTartalom = true; }
else if (ch == '\r') { } // a CRLF „\r” részét elhagyjuk
else if (ch == '\n')
{
if (vanTartalom || mezo.Length > 0)
{
mezok.Add(mezo.ToString());
sorok.Add(mezok.ToArray());
}
mezok.Clear();
mezo.Clear();
vanTartalom = false;
}
else { mezo.Append(ch); vanTartalom = true; }
}
if (idezoben) { throw new FormatException("Lezáratlan idézőjel a fájl végén."); }
if (vanTartalom || mezo.Length > 0) // az utolsó sor sortörés nélkül is rekord
{
mezok.Add(mezo.ToString());
sorok.Add(mezok.ToArray());
}
return sorok;
}
// az elválasztó felismerése az első sorból (az idézőjelen belüli karaktereket nem számolja)
public static char ElvalasztoTipp(string elsoSor)
{
char[] jeloltek = { ';', ',', '\t', '|' };
int legtobb = -1;
char nyertes = ';';
foreach (char j in jeloltek)
{
int db = 0;
bool idez = false;
foreach (char ch in elsoSor)
{
if (ch == '"') { idez = !idez; }
else if (!idez && ch == j) { db++; }
}
if (db > legtobb) { legtobb = db; nyertes = j; }
}
return nyertes;
}
}idezoben jelző mondja meg, hogy az aktuális karakter az idézett rész belsejében van-e. Ugyanaz az állapotgép gondolkodás, mint az egéres húzásnál (9.6 lecke): egy jelző és szabályok.Peek() a következő karaktert mutatja olvasás nélkül: így döntjük el, hogy az idézőjel a megkettőzött ("") vagy a záró. -1 a fájl vége.FormatException), nem találgat. A hívó try–catch blokkal kezeli, és érthető üzenetet mutat.vanTartalom jelző a valódi üres rekordot (például ;;) megkülönbözteti a teljesen üres sortól.Jósolj!
A feldolgozó a "Marker ""XL""";480 sort kapja (; elválasztóval). Milyen mezőket ad?
Jóslat ellenőrzése
Két mezőt: Marker "XL" (a megkettőzött idézőjelek egy idézőjellé váltak, a külső idézőjelek elmaradtak) és 480.
Oszlopok megfeleltetése, számok és kultúra
// a fejléc neveinek egységesítése: kisbetű, ékezet és szóköz nélkül (a „Név”, „nev”, „ NÉV ” ugyanaz)
private string Egyseges(string s)
{
string bontott = s.Trim().ToLowerInvariant().Normalize(NormalizationForm.FormD);
StringBuilder sb = new StringBuilder();
foreach (char c in bontott)
{
if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark && c != ' ') { sb.Append(c); }
}
return sb.ToString();
}
// a fejléc alapján: mező neve → oszlop sorszáma
Dictionary<string, int> oszlopok = new Dictionary<string, int>();
for (int i = 0; i < fejlec.Length; i++) { oszlopok[Egyseges(fejlec[i])] = i; }
int arOszlop;
if (!oszlopok.TryGetValue("ar", out arOszlop)) { Hiba("Hiányzik az Ár oszlop."); }
// számok: a kultúra számít! A magyar „120,5” invariáns kultúrával NEM 120,5
CultureInfo magyar = CultureInfo.GetCultureInfo("hu-HU");
CultureInfo invariant = CultureInfo.InvariantCulture;
double ar;
if (!double.TryParse("120,5", NumberStyles.Float, magyar, out ar)) { /* hibás szám */ } // 120,5
double.TryParse("120,5", NumberStyles.Float, invariant, out ar); // 1205 (a vessző ezres elválasztó!)
// dátum: a formátumot is megadjuk
DateTime datum;
bool ok = DateTime.TryParseExact("2026. 10. 28.", "yyyy. MM. dd.", magyar, DateTimeStyles.None, out datum);double.Parse("120,5") a gép kultúrájától függ. Fájlból olvasott számnál mindig add meg a kultúrát: a magyar CSV (;) általában tizedesvesszős, a vesszős (,) angol CSV tizedespontos. Az Invariant kultúra a tizedespontot várja.TryParse nem dob kivételt hibás szövegnél, false értéket ad: sorról sorra ellenőrizhető, és a hibás sor jelenthető. A Parse kivétele a teljes betöltést megszakítaná.sor.Length > oszlop), különben IndexOutOfRangeException.Jósolj!
A program double.Parse("120,5", CultureInfo.InvariantCulture) hívással olvas számot, a vessző tizedesjelnek szánva. Mi lesz az eredmény?
Jóslat ellenőrzése
1205: az invariáns kultúra a vesszőt ezres elválasztónak tekinti, és kivétel nélkül tízszeresére rontja az értéket. A Parse alapértelmezett NumberStyles.Float beállításával ez csendes adathiba; magyar fájlnál hu-HU kultúrát adj meg.
Biztonságos írás: idézés, Excel, képletvédelem
// egy mező megfelelő idézése
public static string Mezo(string ertek, char elvalaszto)
{
if (ertek == null) { return ""; }
// képletbeszúrás elleni védelem: az Excel a = + - @ jellel kezdődő cellát képletként értelmezi
if (ertek.Length > 0 && ("=+-@".IndexOf(ertek[0]) >= 0 || ertek[0] == '\t' || ertek[0] == '\r'))
{
ertek = "'" + ertek; // az aposztróf szöveggé teszi (csak SZÖVEGES mezőkre alkalmazd, a számokra nem!)
}
bool idezni = ertek.IndexOf(elvalaszto) >= 0 || ertek.IndexOf('"') >= 0 || ertek.IndexOf('\n') >= 0 || ertek.IndexOf('\r') >= 0
|| ertek != ertek.Trim();
return idezni ? '"' + ertek.Replace("\"", "\"\"") + '"' : ertek;
}
public static string Sor(char elvalaszto, params string[] mezok)
{
return string.Join(elvalaszto.ToString(), mezok.Select(m => Mezo(m, elvalaszto)));
}
// mentés Excel-barát módon: ; elválasztó, tizedesvessző, UTF-8 BOM-mal
using (StreamWriter iro = new StreamWriter(ut, false, new UTF8Encoding(true)))
{
iro.WriteLine(Csv.Sor(';', "Név", "Ár", "Megjegyzés"));
foreach (Termek t in termekek)
{
iro.WriteLine(Csv.Sor(';', t.Nev, t.Ar.ToString("0.##", magyar), t.Megjegyzes)); // az ár számként, magyar tizedesjellel
}
}=, +, -, @ jellel kezdődik (például =HYPERLINK(...)), az Excel képletként végrehajtja, amikor a fájlt megnyitják. Felhasználótól származó szöveg (megjegyzés, név) CSV-be írásakor ezért előtagot (') kap. A számmezőt (a -5 mínusz előjelét) nem szabad így módosítani: azt számként, külön formázva írd.; elválasztó magyar Excelben automatikusan oszlopokra bontja a sort.ToString("0.##", magyar)): a ToString() a gép beállítását használja, és más gépen más tizedesjelet adhat.Jósolj!
A felhasználó a „Megjegyzés” mezőbe azt írja: =HYPERLINK("http://rossz.example";"Kattints!"). A program idézés és védelem nélkül írja a CSV-be, majd az iskolai titkárság megnyitja Excelben. Mi történik?
Jóslat ellenőrzése
Az Excel a cellát képletként értelmezi: kattintható hivatkozást (veszélyesebb esetben külső adat hívását) hoz létre. Ez a CSV-injekció. A szöveges mező elé írt ' előtag szöveggé teszi. Felhasználói bemenetet CSV-be sosem írunk védelem nélkül.
Mit tegyél ide: a CSV-importáló: az előnézet, az oszlopok megfeleltetése, a betöltött sorok és a hibalista.Javasolt fájlnév: winforms-10-5-csv-importalo.webp · Alt szöveg: A CSV-importáló program
Hibakeresés: te vagy a hibadetektív
Mi a baj, és hogyan javítod?
Gondold végig az okot, és csak utána nyisd le a megoldást.
1. Eltolódnak az oszlopok bizonyos soroknál
Néhány sorban az ár a megjegyzés oszlopba kerül. Az elválasztót tartalmazó mezőt a Ok és javítás
Split szétvágja. Használj idézőjel-kezelő feldolgozót (Csv.Elemez), és exportnál idézz.
2. IndexOutOfRangeException egy sor feldolgozásánál
string ar = sor[2]; // a sor csak 2 mezőből állHiba és javítás
Ellenőrizd a mezőszámot (sor.Length > 2), és a hibás sort jelentsd (sorszám + ok), ne állítsd le az egész betöltést.
3. Az ár tízszeresére nőtt
double ar = double.Parse("120,5", CultureInfo.InvariantCulture); // 1205Hiba és javítás
Az invariáns kultúra a vesszőt ezres elválasztónak veszi. Magyar formátumhoz hu-HU; a TryParse a hibás szöveget is jelzi.
4. Az ékezetek hibásak az Excelben
A mentett CSV-t az Excel összekuszálva mutatja. BOM nélküli UTF-8 (10.3 lecke). Mentsd Ok és javítás
new UTF8Encoding(true) kódolással.
5. Az első oszlop neve furcsa: "\uFEFFNév"
A fejléc első mezője nem egyezik a „Név” szöveggel. A fájl elején a BOM karakter (Ok és javítás
\uFEFF) maradt a szövegben (kézi dekódolásnál). A StreamReader a BOM-ot elveszi; kézzel TrimStart('\uFEFF').
6. Az utolsó sor hiányzik
A fájl végén sortörés nélküli sor nem töltődik be. A feldolgozó csak a Ok és javítás
\n-nél zárta a rekordot. A fájl végén a nyitott rekordot is le kell zárni (lásd a Csv.Elemez végét).
Mini projekt: CSV-importáló
Készíts importáló varázslót, amely tetszőleges CSV-ből betölti a termékeket, és jelenti a hibákat:
- Felület: Fájl megnyitása... (a 10.4 lecke segédosztályával),
cmbKodolaséscmbElvalaszto(automatikus, ;, ,, Tab, |),chkFejlec(Az első sor fejléc),dgvElonezet(az első 20 sor), négyComboBoxa megfeleltetéshez (Név, Ár, Darab, Kategória) és Importálás; aluldgvJok(a betöltött termékek) éslstHibak(a hibás sorok); Hibák mentése..., Termékek exportálása.... - Beolvasás: a bájtok dekódolása a 10.3 lecke szabályai szerint (BOM → érvényes UTF-8 → Windows-1250); az elválasztó tippje az első sorból (
ElvalasztoTipp); aCsv.Elemez; lezáratlan idézőjelnél érthető hibaüzenet. - Előnézet: az első 20 rekord a rácsban; ha van fejléc, az oszlopok a fejléc neveit kapják, különben Oszlop 1…; a megfeleltető legördülők automatikusan kiválasztják az egyező (egységesített) nevű oszlopot.
- Importálás és ellenőrzés: soronként: a Név kötelező (nem üres, legfeljebb 60 karakter); az Ár szám ≥ 0 (
TryParse, a kultúrát a felhasználó választja: magyar vagy angol); a Darab egész ≥ 0; hiányzó mező, hibás szám és túl hosszú név külön hibaüzenetet kap; a hibás sor a sorszámmal és okkal kerül a hibalistára, a jó sor a rácsba. - Összegzés: 128 sorból 120 betöltve, 8 hibás; az ismétlődő nevek jelzése (nem hiba, figyelmeztetés).
- Hibák mentése: a hibás sorok eredeti tartalmával és az okkal egy új CSV-be (a felhasználó kijavíthatja, és újra importálhatja); az eredeti fájl nem változik.
- Exportálás: a betöltött termékek CSV-be:
;elválasztó, magyar tizedesvessző, UTF-8 BOM, idézés, képletvédelem a szöveges mezőkre; az árak számként, nem szövegként. - Hibatűrés: üres fájl, csak fejléc, bináris fájl, 100 000 soros fájl (figyelmeztetés a lassúságra: 11. fejezet), a nyitott fájl zárolása.
- Teszteld: idézőjeles és többsoros mezők,
""az idézőben, hiányos sorok, szóközös számok ( 120,5 ), tizedespontos fájl magyar beállítással, fejléc nélküli fájl, kódolások, export Excelben megnyitva (ékezetek, a=kezdetű megjegyzés szövegként marad).
Megoldás megnyitása (próbáld ki előbb önállóan!)
A Csv osztályt a lecke mutatja; itt a varázsló logikája áll: megfeleltetés, soronkénti ellenőrzés és export.
public class Termek
{
public string Nev { get; set; } = "";
public double Ar { get; set; }
public int Darab { get; set; }
public string Kategoria { get; set; } = "";
}
private List<string[]> rekordok = new List<string[]>();
private string[] fejlec = new string[0];
private BindingList<Termek> termekek = new BindingList<Termek>();
private void Form1_Load(object sender, EventArgs e)
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
cmbElvalaszto.Items.AddRange(new object[] { "Automatikus", ";", ",", "Tab", "|" });
cmbElvalaszto.SelectedIndex = 0;
cmbKultura.Items.AddRange(new object[] { "Magyar (120,5)", "Angol (120.5)" });
cmbKultura.SelectedIndex = 0;
dgvJok.DataSource = termekek;
}
private string Dekodol(byte[] b)
{
if (b.Length >= 3 && b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF) { return new UTF8Encoding(true).GetString(b, 3, b.Length - 3); }
try { return new UTF8Encoding(false, true).GetString(b); }
catch (DecoderFallbackException) { return Encoding.GetEncoding(1250).GetString(b); }
}
private char Elvalaszto(string szoveg)
{
switch (cmbElvalaszto.SelectedIndex)
{
case 1: return ';';
case 2: return ',';
case 3: return '\t';
case 4: return '|';
default:
int vege = szoveg.IndexOfAny(new char[] { '\r', '\n' });
return Csv.ElvalasztoTipp(vege < 0 ? szoveg : szoveg.Substring(0, vege));
}
}
private string Egyseges(string s)
{
string bontott = s.Trim().ToLowerInvariant().Normalize(NormalizationForm.FormD);
StringBuilder sb = new StringBuilder();
foreach (char c in bontott) { if (CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark && c != ' ') { sb.Append(c); } }
return sb.ToString();
}
private void btnMegnyit_Click(object sender, EventArgs e)
{
string ut = FajlValaszto.Megnyit(this, "CSV (*.csv;*.txt)|*.csv;*.txt|Minden fájl (*.*)|*.*", null);
if (ut == null) { return; }
try
{
string szoveg = Dekodol(File.ReadAllBytes(ut));
char elv = Elvalaszto(szoveg);
using (StringReader olvaso = new StringReader(szoveg))
{
rekordok = Csv.Elemez(olvaso, elv);
}
}
catch (FormatException ex) { MessageBox.Show(this, ex.Message, "Hibás CSV", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; }
catch (IOException ex) { MessageBox.Show(this, "A fájl nem olvasható: " + ex.Message, "Megnyitás", MessageBoxButtons.OK, MessageBoxIcon.Error); return; }
if (rekordok.Count == 0) { tsslUzenet.Text = "A fájl üres."; return; }
Elonezet();
}
private void Elonezet()
{
int oszlopDb = rekordok.Max(r => r.Length);
fejlec = chkFejlec.Checked ? rekordok[0] : Enumerable.Range(1, oszlopDb).Select(i => "Oszlop " + i).ToArray();
System.Data.DataTable dt = new System.Data.DataTable();
for (int i = 0; i < oszlopDb; i++) { dt.Columns.Add(i < fejlec.Length && fejlec[i].Length > 0 ? fejlec[i] + " (" + (i + 1) + ")" : "Oszlop " + (i + 1)); }
foreach (string[] r in rekordok.Skip(chkFejlec.Checked ? 1 : 0).Take(20))
{
dt.Rows.Add(Enumerable.Range(0, oszlopDb).Select(i => i < r.Length ? r[i] : "").ToArray());
}
dgvElonezet.DataSource = dt;
foreach (ComboBox cb in new[] { cmbNev, cmbAr, cmbDarab, cmbKategoria })
{
cb.Items.Clear();
cb.Items.Add("(nincs)");
cb.Items.AddRange(Enumerable.Range(0, oszlopDb).Select(i => (object)(i + 1) + ": " + (i < fejlec.Length ? fejlec[i] : "")).ToArray());
cb.SelectedIndex = 0;
}
Automatikus(cmbNev, "nev", "termek", "megnevezes");
Automatikus(cmbAr, "ar", "egysegar", "price");
Automatikus(cmbDarab, "darab", "db", "mennyiseg");
Automatikus(cmbKategoria, "kategoria", "tipus");
}
private void Automatikus(ComboBox cb, params string[] nevek)
{
if (!chkFejlec.Checked) { return; }
for (int i = 0; i < fejlec.Length; i++)
{
if (nevek.Contains(Egyseges(fejlec[i]))) { cb.SelectedIndex = i + 1; return; }
}
}
private void chkFejlec_CheckedChanged(object sender, EventArgs e) { if (rekordok.Count > 0) { Elonezet(); } }
private string Mezo(string[] sor, ComboBox cb)
{
int i = cb.SelectedIndex - 1; // 0: (nincs)
return i >= 0 && i < sor.Length ? sor[i].Trim() : null;
}
private void btnImport_Click(object sender, EventArgs e)
{
if (cmbNev.SelectedIndex <= 0 || cmbAr.SelectedIndex <= 0) { MessageBox.Show(this, "A Név és az Ár oszlop megadása kötelező.", "Importálás"); return; }
CultureInfo kultura = CultureInfo.GetCultureInfo(cmbKultura.SelectedIndex == 0 ? "hu-HU" : "en-US");
termekek.Clear();
lstHibak.Items.Clear();
hibasSorok.Clear();
int sorszam = chkFejlec.Checked ? 1 : 0;
HashSet<string> nevek = new HashSet<string>(StringComparer.CurrentCultureIgnoreCase);
int ismetlodo = 0;
foreach (string[] sor in rekordok.Skip(chkFejlec.Checked ? 1 : 0))
{
sorszam++;
string nev = Mezo(sor, cmbNev), arSz = Mezo(sor, cmbAr), db = Mezo(sor, cmbDarab);
string ok = null;
double ar = 0;
int darab = 0;
if (string.IsNullOrWhiteSpace(nev)) { ok = "hiányzó név"; }
else if (nev.Length > 60) { ok = "a név túl hosszú (" + nev.Length + " karakter)"; }
else if (arSz == null || !double.TryParse(arSz, NumberStyles.Float, kultura, out ar) || ar < 0 || double.IsInfinity(ar)) { ok = "hibás ár: '" + arSz + "'"; }
else if (cmbDarab.SelectedIndex > 0 && (db == null || !int.TryParse(db, NumberStyles.Integer, kultura, out darab) || darab < 0)) { ok = "hibás darabszám: '" + db + "'"; }
if (ok != null)
{
lstHibak.Items.Add(sorszam + ". sor: " + ok);
hibasSorok.Add(new KeyValuePair<string[], string>(sor, sorszam + ". sor: " + ok));
continue;
}
if (!nevek.Add(nev)) { ismetlodo++; }
termekek.Add(new Termek { Nev = nev, Ar = ar, Darab = darab, Kategoria = Mezo(sor, cmbKategoria) ?? "" });
}
tsslUzenet.Text = termekek.Count + " sor betöltve, " + hibasSorok.Count + " hibás" + (ismetlodo > 0 ? ", " + ismetlodo + " ismétlődő név (figyelmeztetés)" : "");
}
private List<KeyValuePair<string[], string>> hibasSorok = new List<KeyValuePair<string[], string>>();
private void btnHibakMent_Click(object sender, EventArgs e)
{
if (hibasSorok.Count == 0) { return; }
int idx;
string ut = FajlValaszto.Ment(this, "CSV (*.csv)|*.csv", null, "hibas-sorok", out idx);
if (ut == null) { return; }
try
{
using (StreamWriter iro = new StreamWriter(ut, false, new UTF8Encoding(true)))
{
iro.WriteLine(Csv.Sor(';', "Hiba", "Eredeti sor"));
foreach (var h in hibasSorok) { iro.WriteLine(Csv.Sor(';', h.Value, string.Join(" | ", h.Key))); }
}
}
catch (IOException ex) { MessageBox.Show(this, "A mentés nem sikerült: " + ex.Message, "Mentés", MessageBoxButtons.OK, MessageBoxIcon.Error); }
}
private void btnExport_Click(object sender, EventArgs e)
{
if (termekek.Count == 0) { return; }
int idx;
string ut = FajlValaszto.Ment(this, "CSV (*.csv)|*.csv", null, "termekek", out idx);
if (ut == null) { return; }
CultureInfo hu = CultureInfo.GetCultureInfo("hu-HU");
try
{
using (StreamWriter iro = new StreamWriter(ut, false, new UTF8Encoding(true))) // BOM az Excelnek
{
iro.WriteLine(Csv.Sor(';', "Név", "Ár", "Darab", "Kategória"));
foreach (Termek t in termekek) { iro.WriteLine(Csv.Sor(';', t.Nev, t.Ar.ToString("0.##", hu), t.Darab.ToString(CultureInfo.InvariantCulture), t.Kategoria)); }
}
tsslUzenet.Text = "Exportálva: " + Path.GetFileName(ut);
}
catch (IOException ex) { MessageBox.Show(this, "Az exportálás nem sikerült: " + ex.Message, "Exportálás", MessageBoxButtons.OK, MessageBoxIcon.Error); }
}Mit tegyél ide: a kész CSV-importáló egy hibás sorokat is tartalmazó fájllal.Javasolt fájlnév: winforms-10-5-kesz.webp · Alt szöveg: A kész CSV-importáló
Három önálló gyakorlófeladat
Egyszerre csak egyet csinálj: tervezd meg, írd meg, futtasd, ellenőrizd, és csak utána nyisd ki a megoldást.
1. Osztálylista statisztikája
Egy nev;osztaly;jegy CSV-ből számold ki osztályonként az átlagot. Mit használsz? Az elemzett rekordok LINQ Egy lehetséges megoldás
GroupBy csoportosítása az osztály szerint, az átlag a Average; a jegyet TryParse olvassa, a hibás sort átugorja és jelenti.var atlagok = rekordok.Skip(1).Where(r => r.Length >= 3).Select(r => new { Osztaly = r[1], Jegy = double.TryParse(r[2], NumberStyles.Float, hu, out double j) ? (double?)j : null }).Where(x => x.Jegy != null).GroupBy(x => x.Osztaly).Select(g => new { Osztaly = g.Key, Atlag = g.Average(x => x.Jegy.Value) });
2. Táblázat exportja DataGridView-ból
Exportáld a Csak a DataGridView tartalmát CSV-be, a rejtett oszlopok nélkül. Mire figyelsz?Egy lehetséges megoldás
Visible oszlopokat, a DisplayIndex sorrendjében; a cellák Value értékét számként formázva (kultúra), a szöveges mezőket idézve és képletvédelemmel; az új sor sort (IsNewRow) kihagyva.
3. Nagy CSV: soronkénti feldolgozás
Egy 500 MB-os CSV-ből csak az első 1000 rekordot kell megnézni. Mi a hatékony megoldás? A Egy lehetséges megoldás
Csv.Elemez TextReader paramétert kap: a StreamReader (nem a teljes szöveg) átadásával, és egy változat, amely IEnumerable<string[]> lustán ad vissza (yield return), megáll az 1000. rekordnál. A teljes fájl beolvasása elkerülhető.
Gyakorlótár: 6 további feladat
Három szinten gyakorolhatsz: alap, közepes és kihívás. Csak a próbálkozás után nyisd le a megoldást.
Alap szint
1. Idézés
Hogyan kerül CSV-be (; elválasztóval) a Kovács "Béla"; 5.a szöveg?Megoldás megnyitása
"Kovács ""Béla""; 5.a": a mező idézőjelbe kerül (elválasztót tartalmaz), a belső idézőjelek megkettőződnek.
2. Melyik elválasztó?
Melyik elválasztót használja a magyar Excel, és miért? A pontosvesszőt: a magyar tizedesjel a vessző, ezért az nem lehet mezőelválasztó.Megoldás megnyitása
Közepes szint
3. Fejléc nélküli fájl
Hogyan döntöd el automatikusan, hogy van-e fejléc? Heurisztika: az első sor mezői mind szövegek (nem számok), míg az alatta lévő soroknál van szám. Nem tévedhetetlen: a felhasználó jelölőnégyzettel felülbírálhatja.Megoldás megnyitása
4. Dátum a CSV-ben
A fájlban 2026. 10. 28. és 2026-10-28 is előfordul. Hogyan olvasod?Megoldás megnyitása
DateTime.TryParseExact több formátum tömbjével (new[] { "yyyy. MM. dd.", "yyyy-MM-dd" }), a kultúra megadásával; a nem illeszkedő érték hibás sor.
Kihívás szint
5. Lusta feldolgozó
Alakítsd a Az Csv.Elemez metódust IEnumerable<string[]> típusúra. Mi az előnye?Megoldás megnyitása
yield return rekordonként ad vissza: a hívó korán abbahagyhatja, és a teljes fájl nem kerül a memóriába. A FormatException a felsorolás közben keletkezik.
6. Hibás fájl javítása
A fájl egy sorában lezáratlan idézőjel van. Hogyan tudnád a többi sort megmenteni? A lezáratlan idézőjel az összes további sort elnyelné. Védekezés: legfeljebb N sorig (például 50) engedélyezett többsoros mező; azon túl a feldolgozó a rekordot hibásnak jelöli, és az idézőjel-állapotot a következő sor elején visszaállítja. Ez heurisztika, a legjobb a hibás fájl jelentése.Megoldás megnyitása
Gyors önellenőrzés
Válaszolj fejben, és csak utána nyisd le a választ. Az idézőjeles mezőben lehet elválasztó, idézőjel és sortörés; a Megkettőzéssel: Karakterenként olvas, és az A tizedesjel kultúrafüggő: a magyar „120,5” invariáns kultúrával 1205 lesz. A ANSI kódolásúnak veszi: az ékezetek hibásak lesznek; BOM-os UTF-8 kell. Jelented (sorszám + ok), a jó sorokat betöltöd, a hibásakat kijavításra elmented.1. Miért nem elég a Split a CSV-hez?
Split ezeket rosszul vágja.2. Hogyan jelöli a CSV az idézőjelet a mezőben?
"".3. Mi az állapotgépes feldolgozó lényege?
idezoben jelző dönti el, hogy az elválasztó/sortörés elválaszt vagy a mező része.4. Miért kell kultúrát adni a számolvasásnál?
5. Mi a CSV-injekció?
=, +, -, @ kezdetű szöveget az Excel képletként hajtja végre; a szöveges mezők elé ' előtag kell.6. Mit tesz az Excel BOM nélküli UTF-8 CSV-vel?
7. Mit kezdesz a hibás sorokkal?
Mit tudsz már?
- Ismered a CSV szabályait, és idézőjel-kezelő feldolgozót írsz.
- Felismered az elválasztót, és fejléc szerint megfelelteted az oszlopokat.
- Számot és dátumot olvasol a megfelelő kultúrával.
- Excel-barát, védett CSV-t írsz (BOM, idézés, képletvédelem).
- A hibás sorokat jelented, nem veszíted el az adatot.
Következő lecke: JSON szerializálás és visszaolvasás
Objektumok mentése és betöltése JSON formátumban: System.Text.Json, beállítások, ékezetek, enumok, dátumok és a hibás fájlok kezelése.



