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

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

V tomto článku 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.

Jdeme tedy programovat

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 komponentu 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 komponentu ListBox pro zobrazení uživatelů. Nakonec si přejmenujeme naše komponenty ze základního jména na nějaké, abychom se v nich vyznali. Viz. obrázek.

Pojmenování komponent 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 komponenty 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 komponent
        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ď už máme vcelku funkční aplikaci. Když aplikaci spustíte a zkusíte uživatele přidat, uvidíte, že se přidal vlastně jen "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 vyvtoř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 serializuje automaticky. Nyní se vrátíme zpátky do designeru a u formuláře najdeme Event(událost) OnClosing, 2x klikneme a v 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ž teď spustíte program, přidáte uživatele a program zavřete, kolekce uživatelů se serializuje a uloží do NazevProjektu/Bin/De­bug/uzivatele­.xml. Když soubor otevřete, 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í 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ří instanci a potom postupně zadání parametry jak je čte ze souboru (nebo jiného streamu).
  • Nemůžete serializovat komponentu, ať už defaultní a nebo vaší udělanou(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í 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.

 

Stáhnout

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

 

  Aktivity (1)

Článek pro vás napsal Jan Vargovský
Avatar
Autor se věnuje vývoji enterprise softwaru v .NETu.

Jak se ti líbí článek?
Celkem (12 hlasů) :
4.083334.083334.083334.08333 4.08333


 


Miniatura
Předchozí článek
LINQ to XML v C# .NET
Miniatura
Všechny články v sekci
Práce se soubory v C#

 

 

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

Avatar
Jan Vargovský
Redaktor
Avatar
Jan Vargovský:

Můžeš to trošku rozvést? Instance serializeru je vázaná s typem, který budeš serializovat. Nemůžeš tam předat typ X a pak chtít po něm, aby serializoval typ Y.

 
Odpovědět 17. května 21:11
Avatar
Xan95
Člen
Avatar
Odpovídá na Jan Vargovský
Xan95:

Hlavní otázka byla proč není Serializer generický, když se váže s typem. Přijde mi logičtější kdyby byl generický jako např. List<T>.

Při užití generické verze bych i ve Visual Studiu podle typu hned věděl, jaký typ serializuje. Připadá mi, že by generický byl lepší... Proto mě zajímá proč? Byly generické typy až v nějaké novější verzi? Nebo má třeba genericita v tomhle nějaká omezení?

 
Odpovědět 18. května 0:29
Avatar
Jan Vargovský
Redaktor
Avatar
Odpovídá na Xan95
Jan Vargovský:

Protože to není potřeba. Klidně ho generický napsat můžeš. Ale snad víš co tam strkáš, tak nepotřebuješ IntelliSense...

 
Odpovědět 18. května 21:00
Avatar
Garrom Orc Shaman:

Mohl bych se zeptat jak serializovaný soubor šifrovat, nenašel jsem na to žádný článek(možná jen hledám na špatném místě...i když kde jinde než v práci se soubory by mělo šifrování být ?)

-Garrom

Editováno 10. října 10:17
Odpovědět 10. října 10:17
We're orcs, maybe we are not always wise or beautiful, but we will always be strong, outnumbered and well armed
Avatar
Odpovídá na Garrom Orc Shaman
Luboš Běhounek (Satik):

Ten stream bys prohnal jeste pres jinej stream, pokud ti staci data udelat textove necitelny, tak muzes treba i vyuzit DeflateStream, kterej zaroven pouzije kompresi.

Pripadne si napsat vlastni stream, kterej to nejak zasifruje - budto obycejnym xorem nebo uz pak necim pokrocilejsim.

Odpovědět 10. října 10:47
:)
Avatar
Odpovídá na Luboš Běhounek (Satik)
Garrom Orc Shaman:

takže místo klasického SreamWriter bych použil něco co text v .xml zašifruje. Existuje způsob jak vytvořit nějaký klíč pole kterého se to bude šifrovat ?
ps. Jako vážně, největší česká online učebnice C# a není tu nic o šifrované serializaci ?
-Garrom

Odpovědět 10. října 14:57
We're orcs, maybe we are not always wise or beautiful, but we will always be strong, outnumbered and well armed
Avatar
Odpovídá na Garrom Orc Shaman
Luboš Běhounek (Satik):

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. října 15:12
:)
Avatar
Jan Vargovský
Redaktor
Avatar
Odpovídá na Garrom Orc Shaman
Jan Vargovský:

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. října 15:51
 
Odpovědět 10. října 15:48
Avatar
Odpovídá na Jan Vargovský
Garrom Orc Shaman:

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. října 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:
Objekt typu ... nelze serializovat. Vícerozměrná pole nejsou podporována.

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

 
Odpovědět 10. října 19:43
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 28. Zobrazit vše