XML i serializacja w C#

idrys

<font size="4">XML i serializacja w C#</span>

filed in .NET, Programowanie on Jul.26, 2008

Jakiś czas temu natknąłem się na coś, co nie jest niestety moją pasją (delikatnie mówiąc). Było to parsowanie XML-a. Musiałem sparsować niewielki, nieskomplikowany pliczek XML co dostarczyło mi ładnych parę minut mało przyjemnej pracy. W końcu, po sprawdzeniu czy wszystko działa, triumfalnym wzrokiem spojrzałem na kod. “To ja napisałem coś tak paskudnego? o_O” - pomyślałem. Widok zagnieżdżonego while-a + jakieś if-y + switch…śmierć. Pewnie można to było upiększyć ale zajęłoby mi to pewnie kolejne parędziesiąt minut nieprzyjemnej pracy. Z pomocą przyszedł kumpel z pracy, który zasugerował użycia serializacji.

Serializacja klas, a właściwie serializacja obiektów klas to proces ich translacji na strumień bajtów. Ten artykuł ma na celu przedstawienie jedynie małego wycinka możliwości serializacji a dokładniej mapowania obiektów na pliki XML oraz na odwrót (deserializacja).

Głowną klasą pomagającą nam w serializacji jest XmlSerializer. Tworzymy ją w taki sposób:

XmlSerializer sr = new XmlSerializer(typeof(NaszaSerializowanaKlasa));

XmlSerializer dostarcza nam dwóch podstawowych metod: Serialize i Deserialize. Obie metody do działania potrzebują oczywiście przekazania im jakiegoś strumienia danych. Zazwyczaj takie strumienie tworzymy w ten sposób:

Dla serializacji:

TextWriter tw = new StreamWriter("NaszPlik.xml");

Dla deserializacji:

TextReader tr = new StreamReader("NaszPlik.xml");

Oczywiście logicznym jest, że w przypadku serializacji potrzebujemy jakiegoś writera gdyż będziemy pisać do pliku a w przypadku deserializacji readera gdyż będziemy mapować zawartość pliku na obiekt klasy. Oprócz tego do metody Serialize musimy jeszcze przekazać obiekt klasy, którą serializujemy i tu implementacja zależeć będzie od tego co serializujemy. Stosowny przykład przedstawię na końcu tej części w postaci projektu VS C# 2005 Express.

Oczywiście to jeszcze nie wszystko co musimy zrobić. Żeby nasz serializer wiedział, które elementy naszej klasy są jakimi elementami w pliku XML musimy mu to jakoś powiedzieć. Zazwyczaj będzie tak, że nasza klasa będzie document rootem w pliku dlatego też stosujemy następujący zabieg:

[XmlRoot("NaszRoot")]
public class NaszRoot
{
…
}

Znacznik XmlRoot określa, że dana klasa będzie odpowiadać za root dokumentu. Nazwa roota ma być taka jaka jest w pliku XML i nie musi byc taka sama jak nazwa klasy. Ja jednak dla przejrzystości stosuję takie same nazwy żeby nie było żadnych pomyłek i zbędnego doszukiwania się co czemu odpowiada. Następnie mówimy serializerowi które elementy klasy to elementy document roota w pliku XML:

…
string _privVar;
[XmlElement("PrivVar")]
public string PrivVar
{
get { return _privVar; }
set { _privVar = value; }
}
…

Oanacza to, że w serializowanej klasie musimy sobie stworzyć prywatną zmienną oraz jej set/get. Znacznik XmlElement powie serializerowi, że to jest element. Nic więcej nie musimy robić to w zasadzie jest wszystko co potrzeba do prostej serializacji. Jestem pewien, że z tych strzępów kodu nikt nie będzie w stanie tego dobrze zrozumieć więc podaje link do obiecanego kodu.

serialization1.zip

Przeanalizujmy teraz coś trudniejszego. W poprzednim przypadku rozważaliśmy serializacje/deserializacje jednego obiektu. Co musielibyśmy zrobić gdybyśmy chcieli przemielić w ten sposób jakąś kolekcję? W przedstawionym kodzie próbowaliśmy serializowac dane dotyczące jakiegoś projektu do którego logujemy się na podany adres z podanymi username i password w pliku XML. Jak rozwiązać problem wynikający z przechowania w pliku danych dotyczących więcej niż jednego projektu? Wymagać to będzie większego zaplanowania. Najłatwiej chyba stworzyć 2 klasy. Jedną przechowującą nasze dane dot. projektu i drugą trzymającą listę obiektów klasy Projekt. W ten sposób możemy zmapowac nasz kontener na plik XML używając identycznego sposobu jak przedstawiony wcześniej.

Nasza klasa Projekt dalej pozostaje rootem ale będzie ona dzieckiem klasy trzymającej listę projektów(której też nadamy znacznik XmlRoot). Ciekawym rozwiązaniem, które czyni nasz plik XML przejrzystym jest trzymanie w nim dalej roota o nazwie projekt ale nadanie mu name-a. Taka zmiana wymusi na nas jedną poprawkę w klasie Projekt. Mianowicie nazwa projektu (name) nie będzie już elementem lecz atrybutem document roota. Żeby powiedzieć serializerowi o tym fakcie stosujemy następujący zabieg:

…
[XmlAttribute("name")]
public string ProjName
{
get { return _projName; }
set { _projName = value; }
}
…

I to by było na tyle jeśli chodzi o zmiany. Cała procedura serializacji wygląda identycznie (textreader/writer->xmlserializer->serialize/deserialize). Zrozumieć to w 100% pomoże dołączony kodzik (również projekt VS C# 2005 Express).

Jak widać serializacja pomaga znacznie uprościć od strony naszego kodu proces parsowania XML. Dzięki niej zaoszczędzamy sporo czasu i nie narażamy się na zbędny stres ;) Po więcej informacji odsyłam oczywiście na google (.net/c# xml serialization, .net/c# class serialization etc.)

serialization2.zip

<font size="1">Źródło: http://student.agh.edu.pl/~xer/wordpress/?p=26
Za zgodą autora</span>

4 komentarzy

Z drugiego akapitu wynika, że przy serializacji/deserializacji do dyspozycji jest tylko XML co nie jest prawdą. Jest dostępny zapis binarny jak i zapis do SOAP (w gruncie rzeczy też oparty na XML'u ale sam proces serializacji/deserializacji wygląda inaczej).

Bo traktuje o podstawowych informacjach związanych z serializacją. Ale oczywiście temat zawsze można zmienić czekam na sugestie

Przeniosłem.

Czemu artykuł nazywa się "Podstawy" skoro traktuje o serializacji?