Poniższy artykuł pochodzi z serii Przygotowań do egzaminu 70-536.
Witam w kolejnej lekcji z cyklu 70-536. Dzisiaj powiemy sobie troszkę o serializacji.
Co to jest serializacja?
Serializacja, która mieści się w przestrzeni System.Runtime.Serialization, jest to proces przekształcania obiektu do liniowej sekwencji bajtów, które mogą być przechowywane lub przekazywane. Odwrotnym procesem do serializacji jest deserializacja.
Jak serializować obiekt
Poniższy kod prezentuje jak serializować obiekty. Aby kod zadziałał musimy dołączyć przestrzenie nazw System.IO oraz System.Runtime.Serialization.Formatters.Binary.
1: string data = "This must be stored in a file.";
2: // Tworzenie pliku do którego zapisujemy
3: FileStream fs = new FileStream("SerializedString.Data", FileMode.Create);
4: // Tworzenie obiektu binnary formatter
5: BinaryFormatter bf = new BinaryFormatter();
6: // Używamy obiektu BinnaryFormatter do serializacji danych do pliku
7: bf.Serialize(fs, data);
8: // zamykamy plik
9: fs.Close();
Gdy uruchomimy tak zapisany plik zobaczymy jak wygląda nasz string w formacie binarnym :)
Jak deserializować obiekt
Deserializiacja obiektu pozwala na tworzenie nowego obiektu na podstawie zgromadzonych danych. Poniższy kod pokazuje jak przywrócić nasze dane z pliku, który utworzyliśmy w poprzednim przykładzie:
1: // Otwieramy plik
2: FileStream fs = new FileStream("SerializedString.Data", FileMode.Open);
3: // Tworzymy obiekt BinaryFormatter
4: BinaryFormatter bf = new BinaryFormatter();
5: // tworzymy stringa, który będzie przechowywał nasze dane
6: string data = "";
7: // Deserializujemy dane z pliku
8: data = (string)bf.Deserialize(fs);
9: // zamykamy plik
10: fs.Close();
11: // wyświetlamy rezultat
12: Console.WriteLine(data);
Tworzenie klas, które mogą być serializowane
.NET Framework pozwala nam tworzyć własne klasy, które możemy serializować bądź deserializować. Aby to zrobić, musimy do klasy dodać atrybut np. Serializable. Oczywiście to nie jedyny atrybut, którego używamy podczas budowania klasy z obsługą serializacji, ale o tym w dalszej części.
Wyłączanie niektórych elementów z procesu serializacji
Na początku stwórzmy sobie klasę, w której wszystkie pola są serializowane:
1: [Serializable]
2: class ShoppingCartItem
3: {
4: public int productId;
5: public decimal price;
6: public int quantity;
7: public decimal total;
8: public ShoppingCartItem(int _productID, decimal _price, int _quantity)
9: {
10: productId = _productID;
11: price = _price;
12: quantity = _quantity;
13: total = price * quantity;
14: }
15: }
Czasami jednak zachodzi potrzeba wyłączenia jakiegoś elementu z procesu serializacji. Poniższy kod przedstawia użycie atrybutu NonSerialized. Dzięki temu pole total zostanie pominięty podczas serializacji. Musimy jednak pamiętać, że wartość pola total musi wciąż być liczona przed użyciem “zdeserializowanego” obiektu.
1: [NonSerialized] public decimal total;
Aby włączyć automatyczne inicjalizowanie nonserialized w klasie, należy użyć interfejsu IDeserializationCallback oraz zaimplementować IDeserializationCallback.OnDeserialization. Teraz za każdym razem kiedy klasa zostaje deserializowana, zostaje wywołana metoda IDeserializationCallback.OnDeserialization. Poniższy kod pokazuje zmiany w klasie po zastosowaniu omawianych zmian:
1: [Serializable]
2: class ShoppingCartItem : IDeserializationCallback
3: {
4: public int productId;
5: public decimal price;
6: public int quantity;
7: [NonSerialized] public decimal total;
8: public ShoppingCartItem(int _productID, decimal _price, int _quantity)
9: {
10: productId = _productID;
11: price = _price;
12: quantity = _quantity;
13: total = price * quantity;
14: }
15: void IDeserializationCallback.OnDeserialization(Object sender)
16: {
17: // Po deserializacji obliczamy total
18: total = price * quantity;
19: }
20: }
Zapewnianie kompatybilności wersji
Czasem podczas deserializacji obiektów może wystąpić problem związany z kompatybilnością wersji obiektu. Mamy dwie możliwości aby używać starszych obiektów w nowej wersji naszej aplikacji:
- Zaiplementować własną serializacje, która potrafi zaimportować wcześniejsze obiekty
- Zastosować atrybut OptionalField aby dodać pola, które mogłyby stwarzać problemy ze zgodnością
Poniżej zaprezentowane użycie OptionalField:
1: [Serializable]
2: class ShoppingCartItem : IDeserializationCallback
3: {
4: public int productId;
5: public decimal price;
6: public int quantity;
7: [NonSerialized] public decimal total;
8:
9: [OptionalField] public bool taxable;
Wybieranie formatu serializacji
.NET zawiera dwie klasy do formatowania danych. Zawierają się one w przestrzeni nazwy System.Runtime.Serialization. Obydwie implementują również interfejs IRemotingFormatter. Oto one:)
- BinnaryFormatter – formatowanie, które jest najskuteczniejsze do serializacji obiektów, które będą odczytywane tylko przez aplikacje .NET
- SoapFormatter – jest to formatowanie XML’owe, którego najczęściej używa się gdy chcemy wysyłać dane przez sieć.
Używanie SoapFormatter’a
Aby używać SoapFormatter’a najpierw dodajemy referencję System.Runtime.Serialization.Formatters.Soap.dll. Następnie piszemy taki sam kod jak w przypadku BinnaryFormatter z jedną tylko różnicą: zamieniamy BinnaryFormatter na SoapFormatter :)
Poniżej przykład serializacji Soap:
1: <SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2: <SOAP-ENV:Body>
3: <a1:ShoppingCartItem id="ref-1">
4: <productId>100</productId>
5: <price>10.25</price>
6: <quantity>2</quantity>
7: </a1:ShoppingCartItem>
8: </SOAP-ENV:Body>
9: </SOAP-ENV:Envelope>
Training kit wymienia jeszcze atrybuty używane przy serializacji soap. Są to m.in: SoapAttribute, SoapDefault-
Value, SoapElement, SoapEnum, SoapIgnore, SoapType. Nie będę opisywał do czego służą ponieważ można łatwo znaleźć to w internecie no i artykuł zrobił się troszkę długi.
Dzięki za poświęcony czas.
Kolejny artykuł z serii to 70-536 XML Serialization