Poznáváme C# a Microsoft.NET 41. díl - pokročilé využití serializace

Po minulé díle, který byl věnován úvodu do použití serializace bych na toto téma dnes rád navázal a to povídáním o dalších způsobech využití tohoto zajímavého mechanismu v rámci .NET.

Selektivní serializace členů

Možná některé z vás, při čtení minulého dílu o serializaci, napadlo jestli není nějakým způsobem možné zajistit selektivní serializaci datových členů. Jinými slovy, jestli lze určit nějaké datové členy, jejichž hodnoty nebudou při serializaci přidány do datového proudu vedoucího k cíli serializace. Obvyklým případem takovýchto datových členů, jsou členy, jejichž hodnoty nemají smysl na jiném počítači či v jiném čase. Dalším případem těchto „přechodných“ členů jsou datové členy, které mohou být vypočteny z jiných datových členů.

Tohoto cíle je nám umožněno v .NET frameworku dosáhnout velmi jednoduše a to použitím atributu System.NonSerialized na konkrétní datový člen. Následující zdrojový kód představuje známou třídu Osoba, která má od minulého dílu navíc datový člen vek, který je pomocí zmíněného atributu označen, aby nebyl serializován.

[Serializable]
public class Osoba
{
 private DateTime datumNarozeni;
 private string jmeno;
 private string prijmeni;
 //datovy clen vek nebude serializovan
 [NonSerialized]
 private int vek;

 public Osoba(string Jmeno, string Prijmeni, DateTime DatumNarozeni)
 {
  this.jmeno = Jmeno;
  this.prijmeni = Prijmeni;
  this.datumNarozeni = DatumNarozeni;
  //vek je vypocitan z datumu narozeni
  this.vek = (DateTime.Now - datumNarozeni).Days/365;
 }
 
 //zbytek tridy
}

To, že datový člen vek není při serializaci přidán do serializačního streamu, logicky znamená, že při deserializaci instance, nemůžeme očekávat obnovení hodnoty členu vek. O tom nás ostatně přesvědčuje následující příklad.

static void DeserializacePriklad()
{
 DateTime lDate = new DateTime(1984,2,9);
 Osoba lOsoba = new Osoba("Jan","Novak",lDate);
 Console.WriteLine("Vek osoby pred serializaci : {0}", lOsoba.Vek);
 IFormatter lFormatter = new BinaryFormatter();
 Stream lStream = null;
 try
 {
  lStream = new FileStream("C:/novak.bin",FileMode.Create);
  lFormatter.Serialize(lStream,lOsoba);
  lStream.Position = 0;
  Osoba lDesOsoba = (Osoba) lFormatter.Deserialize(lStream);
  Console.WriteLine("Vek deserializovane osoby je : {0}", lDesOsoba.Vek);
 }
 finally
 {
  lStream.Close();
 }
}

Zmíněný příklad by měl na vaší obrazovku vytvořit následující výstup:

Vek osoby pred serializaci : 21
Vek deserializovane osoby je : 0

Rozhraní IDeserializationCallback

Věřím, že spoustu z vás po přečtení předchozích řádků napadlo, jestli je správné, že hodnota datového členu vek je nulová, když má být vlastně vypočítána z hodnoty datového členu vypodídajícím o datu narození. Vždyt to přeci znamená porušení takzvaných neměných podmínek třídy. Bylo by tedy dobré mít nějakou možnost mít deserializační proces do určité míry pod kontroulou a správnost neměných podmínek daných třídou zajistit. Dosáhnout této kontroly nad deserializačním procesem instancí dané třídy je možné zajistit implementaci rozhraní System.Runtime.Serialization.IDeserializationCallback.

[Serializable]
public class Osoba : System.Runtime.Serialization.IDeserializationCallback
{
 private DateTime datumNarozeni;
 private string jmeno;
 private string prijmeni;
 [NonSerialized]
 private int vek;

 public Osoba(string Jmeno, string Prijmeni, DateTime DatumNarozeni)
 {
  this.jmeno = Jmeno;
  this.prijmeni = Prijmeni;
  this.datumNarozeni = DatumNarozeni;
  this.vek = (DateTime.Now - datumNarozeni).Days/365;
 }

 #region IDeserializationCallback Members

 public void OnDeserialization(object sender)
 {
  this.vek = (DateTime.Now - datumNarozeni).Days/365;
 }

 #endregion
 //zbytek implementace tridy
}

Zmíněne rozhraní předepisuje jedinou metodu, kterou je OnDeserialization v jejímž tělě můžeme zajistit požadované operaci při deserializačním procesu. V našem případě opět vypočítáme věk instance třídy Osoba, pomocí hodnoty datového členu datumNarozeni, který je v tuto chvíli již deserializován.

Vlastní řízení serializace

Jak již víme, tak každá třída, která má podporovat serializaci, musí být označena atributem Serializable. I když je pomocí atributu NonSerialized a implementace rozhraní IDeserializationCallback možné dosáhnout poměrně solidní kontroly nad serializačním a deserializačním procesem, v některých případech potřebujeme mít kontrolu ještě větší. Za tímto účelem je možné, kromě označení třídy atributem Serializable, nechat třídu implementovat rozhraní System.Runtime.Serialization.ISerializable. To nám umožňuje mimo jiné přesně definovat jaké informace budou serializovány. Pro lepší představu uvažujme následující příklad s mou oblíbenou ukázkovou třídou Zarovka.

[Serializable]
public class Zarovka
{
 private bool sviti;
 private DalsiInfo info = new DalsiInfo();

 public Zarovka(){}

 public DalsiInfo Info
 {
  get{return info;}
  set{info = value;}
 }

 public bool Sviti
 {
  get{return sviti;}
 }

 public void Rozsvitit()
 {
  sviti = true;
 }

 public void Zhasnout()
 {
  sviti = false;
 }
}

//neserializovatelna trida pouzita jako clen typu Zarovka
public class DalsiInfo
{
 private string hodnota;

 public DalsiInfo(){}

 public DalsiInfo(string hodnota)
 {
  this.hodnota = hodnota;
 }
  
 public string Hodnota
 {
  get{return hodnota;}
  set{hodnota = value;}
 }
}

Třída Zarovka evidentně nemůže být serializována, protože má jeden ze svých datových členů typu DalsiInfo, který není serializovatelný. Pokud bychom byli autory třídy DalsiInfo, nebyl by to až takový problém, protože bychom měli možnost tuto třídu jako serializovatelnou jednoduše označit. Představme si, ale situaci, kdy nejsme autory této třídy a tedy možnost označit ji atributem Serializable tím pádem nemáme. A to je právě jeden z případů na použítí rozhraní ISerializable.

Rozhraní definuje metodu GetObjectData příjímající parametry typu SerializationInfo a StreamingContext. Pro nás je zajímavý hlavně první parametr, tedy ten typu SerialzationInfo, pomocí kterého přenášíme potřebná data k serializaci a následně i deserializaci instance. Metoda GetObjectData je volána při serializaci konkrétní implementací IFormatteru. Samozřejmě je také potřeba definovat ještě způsob deserializace, tedy operace, které se provedou při vytváření deserializované instance. To se v případě vlastní serializace učiní implementaci speciálního deserializačního konstruktoru. Nyní se podívejme na řešení našeho problému použitím implementace rozhraní ISerializable třídou Zarovka.

/// <summary>
/// Ukazkova trida na vlastni implementaci serializace
/// </summary>
[Serializable]
public class Zarovka : ISerializable
{
 private bool sviti;
 private DalsiInfo info = new DalsiInfo();

 public Zarovka(){}

 public DalsiInfo Info
 {
  get{return info;}
  set{info = value;}
 }

 public bool Sviti
 {
  get{return sviti;}
 }

 public void Rozsvitit()
 {
  sviti = true;
 }

 public void Zhasnout()
 {
  sviti = false;
 }
 #region ISerializable Members

 public void GetObjectData(SerializationInfo serInfo, StreamingContext context)
 {
  serInfo.AddValue("infoHodnota",info.Hodnota);
  serInfo.AddValue("sviti",sviti);
 }

 #endregion

 //deserializacni konstruktor
 private Zarovka(SerializationInfo serInfo,StreamingContext context)
 {
  this.sviti = serInfo.GetBoolean("sviti");
  this.info = new DalsiInfo(serInfo.GetString("infoHodnota"));
 }
}

V metodě GetObjectData jsou do instance třídy SerializationInfo, pomocí různých přetížených verzi metody AddValue, která pod určitým názvem uchová v páru určitou, hodnotu přidány data, která chceme serializovat. Tato metoda ve všech svých přetížených verzí příjímá jako parametr představující hodnotu nějaký ze základních typů, které jsou všechny označeny jako serializovatelné. Jak jste si jistě všimli, název pod kterým je hodnota uchovávána, nemusí vůbec odpovídat názvu datového členu typu. My jsme v našem příkladu využili tohoto způsobu serializace v tom, že jsme místo pokusu o serializaci celé třídy DalsiInfo serializovali pouze její vnitřní hodnotu, která je typu String, takže již žádné problémy nevznikají. Uchování vnitřní hodnoty nám totiž k zachování stavu této třídy úplně stačí.

Tak jako jsme v metodě GetObjectData specifikovali data k serializaci, tak si v deserializačním konstruktoru, který je při deserializaci vždy volán, z parametru SerializationInfo tato data opět vyzvedneme a provedeme potřebné navázání instančních členů. Takže si pomocí metody GetBoolean vyzvedneme hodnotu atributu sviti a vytvoříme instanci třídy DalsiHodnota s použitím získané vnitřní hodnoty (metoda GetString).

Poznámka :  Při implementaci rozhraní ISerializable je vhodné deserializační konstruktor implementovat s omezenou viditelností, aby jej nemohla zavolat kdejaká třída. A jelikož v přítomnosti nějakého uživatelského konstruktoru .NET framework již nevytváří implicitnítní konstruktor, je často potřeba implicitní konstruktor nadefinovat, aby bylo vůbec možné z uživatelského kódu, instance naší třídy vytvářet.

Zdrojové kódy k příkladům jsou ke stažení zde.

Témata článku: Software, Microsoft, Programování, Public, Private, Info

1 komentář

Nejnovější komentáře

  • Pavel Polívka 24. 11. 2007 13:37:53
    Programovou oflline verzi seriálu naleznete ke stažení na...
Určitě si přečtěte

Operační systém běžným počítačům nedal Bill Gates, ale Gary Kildall

Operační systém běžným počítačům nedal Bill Gates, ale Gary Kildall

** Gary Kildall pochopil, že levné výpočetní čipy mohou posloužit jako univerzální počítače pro všechny ** Připravil pro ně proto první operační systém ** Později mu systém vyfoukl Microsoft a nazval ho MS DOS

Včera | Pavel Tronner | 45

Původní Starcraft: Brood War je nyní zdarma. Konec práce! Jde se pařit

Původní Starcraft: Brood War je nyní zdarma. Konec práce! Jde se pařit

** Legendární hra Starcraft je nyní k dispozici zdarma ** Chystá se i nová remasterovaná verze s hezčí grafikou

19.  4.  2017 | Jakub Čížek | 25

Brno otevřelo největší českou dílnu pro bastlíře. Kladívka, vrtačky, 3D tiskárny, laserové řezačky. Je tu vše

Brno otevřelo největší českou dílnu pro bastlíře. Kladívka, vrtačky, 3D tiskárny, laserové řezačky. Je tu vše

** Máte nápad, ale chybí vám stroje a pořádná dílna? ** Chcete postavit ptačí budku, nebo krabičku pro Arduino? ** Brno otevřelo svůj FabLab – laboratoř pro bastlíře

19.  4.  2017 | Jakub Čížek | 31

Před 35 lety měl premiéru legendární počítač ZX Spectrum. Připomeňte si „Gumáka“

Před 35 lety měl premiéru legendární počítač ZX Spectrum. Připomeňte si „Gumáka“

** Slavný osmibitový počítač Sinclair ZX Spectrum byl uveden právě před 35 lety ** Připomeňte si tento průkopnický počítač v tematických článcích ** Podívejte se, jak funguje dnes

Včera | Pavel Tronner | 12


Aktuální číslo časopisu Computer

První test AMD Ryzen

Velké testy: 22 powerbank a 8 bezdrátových setů

Radíme s koupí Wi-Fi routeru

Co dokáží inteligentní domy?