10. díl - Serializace a deserializace v C# .NET

C# .NET Práce se soubory Serializace a deserializace v C# .NET

ONEbit hosting Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, LINQ to XML v C# .NET, jsme si představili technologii LINQ to XML. V dnešním C# .NET tutoriálu si řekneme něco o serializaci a deserializaci.

Serializace je uchování stavu objektu. Trochu odborněji by se to dalo popsat jako konvertování objektu na proud bytů a poté uložení někde do paměti, databáze nebo souboru. Deserializace je opak serializace. Dalo by se říci, že tedy převedete zpátky proud bytů na kopii objektu.

K čemu je to dobré?

Serializace vám umožní uložit stav objektu a potom pomocí deserializace si ho kdykoliv znovu vytvořit. Pomocí serializace se třeba posílají data skrz síť nebo se ukládá nastavení aplikace.

Ukázková aplikace

Založíme si nový projekt typu Windows Forms Application. Poté si vytvoříme nějakou třídu, její instanci bychom chtěli zachovat a při opětovném spuštění aplikace ji mít ve stavu, v jakém jsme ji při zavření aplikace zanechali. Třídu nazveme Uzivatel a dáme jí vlastnosti Jmeno, Prijmeni a DatumNarozeni. Třída by teda mohla vypadat nějak takto (všimněte si, že je veřejná):

public class Uzivatel
{
    public string Jmeno { get; set; }
    public string Prijmeni { get; set; }
    public DateTime DatumNarozeni { get; set; }
}

Na hlavní formulář si přidáme tedy dva Textboxy na jméno a příjmení. Ještě si přidáme kontrolku DateTimePicker, abychom mohli určit datum uživatele. Dále si někde umístíme tlačítko, kterým budeme přidávat uživatele do naší aplikace. Přidáme kontrolku ListBox pro zobrazení uživatelů. Nakonec si přejmenujeme naše kontrolky ze základního jména na nějaké, abychom se v nich vyznali. Viz. obrázek.

Pojmenování komponent ukázkové aplikace na serializaci ve Visual Studio

Dále si vytvoříme kolekci typu List<Uzivatel>, abychom měli uživatele kde v aplikaci uchovávat. Přesuneme se tedy do kódu hlavního formuláře (Form1 nebo u mě FormMain) a dopíšeme si do třídy tuto privátní kolekci.

private List<Uzivatel> uzivatele = new List<Uzivatel>();

Přesuneme se zpátky do designeru formuláře a přidáme si metodu k akci Click u kontrolky btnPridej. Dopíšeme zde kód, který přidá uživatele do naší kolekce. Dále ještě musíme ukázat uživatele v listBoxUzivatele. K tomu nám poslouží vlastnost DataSource.

private void btnPridej_Click(object sender, EventArgs e)
{
        // Vytvoříme nového uživatele s daty z našich kontrolek
        Uzivatel uzivatel = new Uzivatel
        {
                Jmeno = tbJmeno.Text,
                Prijmeni = tbPrijmeni.Text,
                DatumNarozeni = dateTimePicker.Value
        };
        // Přidáme ho do naší kolekce
        uzivatele.Add(uzivatel);
        // Obnovíme zdroj dat našeho listBoxuUzivatele
        listBoxUzivatele.DataSource = null;
        listBoxUzivatele.DataSource = uzivatele;
}

Teď již máme vcelku funkční aplikaci. Když aplikaci spustíte a zkusíte uživatele přidat, uvidíte, že se přidala vlastně jen položka "NazevProjektu­.Uzivatel". Přesuneme se tedy do souboru Uzivatel a přepíšeme metodu ToString(). Třeba nějak takto:

public override string ToString()
{
        return "Jméno: " + Jmeno +
                " Přijmení: " + Prijmeni +
                " Datum Narození: " + DatumNarozeni.ToShortDateString();
}

Zkuste si přidat uživatele teď a vidíme, že se uživatel zobrazuje lidštěji.

Serializace

Nyní konečně můžeme přejít k serializaci dat. Vytvoříme si tedy metodu Serializuj() v kódu formuláře.

private void Serializuj()
{
        try
        {
                // Vytvoříme si XmlSerializer na typ List<Uzivatel>
                XmlSerializer serializer = new XmlSerializer(uzivatele.GetType());

                // Alternativní forma, jak by to šlo také zapsat
                //XmlSerializer serializer = new XmlSerializer(typeof(List<Uzivatel>));

                // Vytvoříme Stream pomocí kterého budeme serializovat
                using (StreamWriter sw = new StreamWriter("uzivatele.xml"))
                {
                        // Zavoláme metodu Serialize(), kde první parametr je Stream
                        // Ten jsme vytvořili o řádek výše
                        // Druhý parametr je objekt, který serializujeme
                        serializer.Serialize(sw, uzivatele);
                }
        }
        catch (Exception ex)
        {
                MessageBox.Show(ex.Message);
        }
}

Využili jsme serializer do formátu XML. Serializerů je několik typů včetně binárního, poskytuje nám je již připravené .NET framework. Nemusíme se tedy starat o nic, instance se serializují automaticky. Nyní se vrátíme zpátky do designeru a u formuláře najdeme Event(událost) OnClosing, 2x klikneme a v obslužném kódu zavoláme naší metodu Serializuj().

private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
{
        Serializuj();
}

Pozn. Název metody se může lišit podle názvu hlavního formuláře. Jestli jste jeho jméno neupravovali, budete mít na začátku nejspíš prefix Form1.

Když nyní spustíme program, přidáme nějaké uživatele a program zavřeme, kolekce uživatelů se serializuje a uloží do NazevProjektu/Bin/Debug/uzivatele.xml. Když soubor otevřeme, měl by být čitelný. U mě soubor vypadá nějak takto:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfUzivatel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Uzivatel>
    <Jmeno>Jan</Jmeno>
    <Prijmeni>Novák</Prijmeni>
    <DatumNarozeni>2013-07-11T17:27:19</DatumNarozeni>
  </Uzivatel>
  <Uzivatel>
    <Jmeno>Jakub</Jmeno>
    <Prijmeni>Špatný</Prijmeni>
    <DatumNarozeni>2013-07-11T17:27:19</DatumNarozeni>
  </Uzivatel>
</ArrayOfUzivatel>

Deserializace

Serializaci máme, tak teď ještě deserializaci. Z pohledu na kód je to trošku těžší, proto raději všechno vysvětlím ještě jednou. Vytvoříme metodu Deserializuj() v kódu hlavního formuláře. Nejprve musíme zjistit, jestli vůbec daný XML soubor s daty existuje. K tomu nám poslouží třída File a metoda Exists(string path), která vrací bool. Do těla podmínky už vytvoříme XmlSerializer na typ našeho Listu uživatelů. Dále vytvoříme StreamReader s cestou k našemu souboru a pak jen zavoláme metodu Deserialize() z třídy XmlSerializer. Je tu ale drobný detail, a to že metoda Deserialize() vrací object. Musíme zde tedy přetypovat, než přiřadíme uložené uživatele k našim stávajícím. Celá metoda tedy vypadá ve finále takto:

private void Deserializuj()
{
        try
        {
                if (File.Exists("uzivatele.xml"))
                {
                        XmlSerializer serializer = new XmlSerializer(uzivatele.GetType());
                        using (StreamReader sr = new StreamReader("uzivatele.xml"))
                        {
                                uzivatele = (List<Uzivatel>)serializer.Deserialize(sr);
                        }
                }
                else throw new FileNotFoundException("Soubor nebyl nalezen");
        }
        catch (Exception ex)
        {
                MessageBox.Show(ex.Message);
        }
}

Tuto metodu zavoláme v události formuláře Load. Přejdeme tedy do designeru a ve vlastnostech (properties) formuláře najdeme událost Load a vytvoříme její obslužnou metodu. V ní zavoláme naší metodu Deserializuj() a k tomu ještě potřebujeme načíst naše uživatele do našeho ListBoxu. Celá metoda vypadá tedy takto:

private void FormMain_Load(object sender, EventArgs e)
{
    Deserializuj();
    listBoxUzivatele.DataSource = uzivatele;
}

Pozn. Název metody se může lišit podle názvu hlavního formuláře. Jestli jste jeho jméno neupravovali, budete mít na začátku nejspíše prefix Form1.

Když aplikaci spustíte a naplníte daty, zavřete a znovu otevřete, měla by obsahovat všechny uživatele, které jste tam přidali.

Závěr

Na závěr bych chtěl říct pár věcí, na které nejspíš přijdete i sami, když budete serializovat objekty vaší aplikace.

  • Třída, kterou serializujete, musí obsahovat bezparametrický konstruktor (nebo žádný parametrický). Je to z toho důvodu, že deserializer si nejdříve vytvoří prázdnou instanci a potom postupně zadává vlastnosti jak je čte ze souboru (nebo jiného streamu).
  • Nemůžete serializovat kontrolky, ať již defaultní a nebo vámi vytvořenou (User Control). Kdybyste ale chtěli, určitě nepotřebujete serializovat úplně všechno, takže si můžete uložit jen ty věci, které opravdu potřebujete.
  • K serializaci patří několik atributů, jako:
    • [XmlIgnore] - Nebude vám danou propertu serializovat.
    • [Serializable()] - Dává se nad deklaraci třídy, dáváte tomu jasně najevo, že se bude objekt serializovat.(Im­plementuje interface ISerializable)
    • [XmlAttribute("Na­me")] změní XML element z párového na nepárový a hodnota dané property bude v atributu Name. Například <Uzivatel Name="Jan"> místo <Jmeno>Jan</Jmeno>.
  • Kdyby jste někdy chtěli serializovat třídu Color, tak ona se sice serializuje, ale v souboru nemá žádnou hodnotu, proto můžete např. serializovat Color jako hexadecimální číslo a při deserializaci si ho jednoduše překonvertujete na barvu.

V příští lekci, Binární soubory v C#, na nás čekají binární soubory.


 

Stáhnout

Staženo 388x (58.1 kB)
Aplikace je včetně zdrojových kódů v jazyce C#

 

 

Článek pro vás napsal Jan Vargovský
Avatar
Jak se ti líbí článek?
13 hlasů
H̴̢̡̛̫̙̖̦͍̥̺̳̪͖̺̫̲͇̗̹̠̥͈̭͔̻̗͚̭̥̝͐̋͒̆̾̅͒̈̐̀̒̔̇̈̔̆̎̔͐̊͆̆̐̊̈͆̂̐̓̓͛̌̈́̈́̅̅̔̚̕̚͠͝͝͝͝ì̸͇͖̹̯̤͇͍̹̥̅͗͆̄̌͆͑̓̈́̓̊̈́͋̈́͛͊͛͂̇͋͒̿̃͐͌̐̚͝͝͝͝͝͝.̶̧̡̧̧̖̫̯̞͖̯̩̠̭̩͇͔̤̱̜̠̠̙͉͉̼̱͓̣͍̱͎͕̦͓̫̗̮̦͍͚̗͕̥̳͚̬̯̞̟͇̻̺̙͙̜͖̰̊͒̌̌̚͜͜͝
Miniatura
Předchozí článek
LINQ to XML v C# .NET
Miniatura
Všechny články v sekci
Práce se soubory v C#
Aktivity (3)

 

 

Komentáře
Zobrazit starší komentáře (24)

Avatar
Luboš Satik Běhounek
Autoredaktor
Avatar
Luboš Satik Běhounek:10.10.2016 15:12

muzes klidne podedit StreamWriter a jen tam pridat sifrovani nebo si na to muzes udelat vlastni stream, to uz je celkem fuk, kam tu sifrovaci funkcnost nacpes.
Klic si muzes vytvorit jakej chces, pokud bys data treba jen XORoval, to zalezi na sifrovacim algoritmu.

Odpovědět 10.10.2016 15:12
:)
Avatar
Jan Vargovský
Redaktor
Avatar
Jan Vargovský:10.10.2016 15:48

Kryptografie je poněkud složitější oříšek než tohle. Kdybys raději řekl nějaký kontext okolo toho šifrování, pak bychom ti mohli doporučit něco, co bude zároveň bezpečné.

Btw,

Jako vážně, největší česká online učebnice C# a není tu nic o šifrované serializaci ?

Největší se nerovná nejlepší. Kvantita neznamená kvalitu ... Navíc serializace a šifrování jsou poněkud odlišné problémy a spojit je už je otázka dát ty volání sekvenčně za sebou.

Editováno 10.10.2016 15:51
 
Odpovědět 10.10.2016 15:48
Avatar
Odpovídá na Jan Vargovský
Garrom Orc Shaman:10.10.2016 16:56

chtěl jsem aby to nebylo lidsky čitelné a ani rozšifrovatelné nějakým procesem (spoustu textových editorů tuto možnost nabízí), aby to šlo rozšifrovat jen tímto heslem. Zároveň bych se nemusel bát že by na něj někdo přišel jelikož Unity3D si své soubory "konzervuje"(mezi tyto soubory patří i .cs skript uložený mezi assety)

Odpovědět 10.10.2016 16:56
We're orcs, maybe we are not always wise or beautiful, but we will always be strong, outnumbered and well armed
Avatar
Petr Stastny
Redaktor
Avatar
Petr Stastny:10.10.2016 19:43
Objekt typu ... nelze serializovat. Vícerozměrná pole nejsou podporována.

Takže... co teď? :D

 
Odpovědět 10.10.2016 19:43
Avatar
Karel Labonek:30. srpna 19:48

Mám problém při serializaci (pokusu o ni na třídě public ve které mám další instance mích tříd ) píše mi to : při reflexi typu došlo k chybě. Potřebuji uložit nastavení šachové hry a i odehrané hry jako takové. Děkuji za radu.

 
Odpovědět 30. srpna 19:48
Avatar
Karel Labonek:30. srpna 20:10

Omluva stačilo kouknout na komentář na demnou a je jasno vícerozměrná pole :-( .

 
Odpovědět 30. srpna 20:10
Avatar
Jan Vargovský
Redaktor
Avatar
Odpovídá na Karel Labonek
Jan Vargovský:30. srpna 20:43

Binárně to už ale uložit jde :)

int[,] a = new[,]
{
    { 1,2 },
    { 3,4 }
};

BinaryFormatter formatter = new BinaryFormatter();
using (var ms = new MemoryStream())
{
    formatter.Serialize(ms, a);

    ms.Position = 0;

    var deserializedA = (int[,])formatter.Deserialize(ms);
}
Editováno 30. srpna 20:44
 
Odpovědět 30. srpna 20:43
Avatar
Odpovídá na Jan Vargovský
Karel Labonek:31. srpna 6:37

Děkuji za tvou reakci, jen ještě jedna začátečnická otázka když mám to pole naplněno svým enumem (něco jako

public enum hodPolicka
    {
        prazdne, bila, bilaKral, cerna, cernaKral, B_C_Kamen, B_C_cernyKral, B_C_bilyKral
    }

)tak bude problém nebo ne? jsem momentálně v práci a nemohu to zkusit . Jen mám takové tušení že enum je stejně nějak vnitřně reprezentován samejma intama je to tak? Děkuji za případnou odpověď a omlouvám se za tyhle ne moc profesionální otázky.

 
Odpovědět 31. srpna 6:37
Avatar
Jan Vargovský
Redaktor
Avatar
Odpovídá na Karel Labonek
Jan Vargovský:31. srpna 8:38

No interně se to zkompiluje na

public enum hodPolicka : int
    {
        prazdne = 0,
        bila = 1,
        bilaKral = 2
        ...
    }

Což je prostě int a bude to fungovat :)

Pro doplnění, enum můžeš reprezentovat všemi celočíselnými hodnotami, tj. sbyte, short, int, long a jejich unsigned varianty.

Editováno 31. srpna 8:40
 
Odpovědět  +1 31. srpna 8:38
Avatar
 
Odpovědět 1. září 15:51
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 34. Zobrazit vše