W poprzednim poście pisałem o serializacji XML'owej, a w tym wrócę jeszcze do tematu. Skorzystamy z tego samego kodu co poprzednio ale tym razem uruchomimy go korzystając z innych serializatorów. Zamieńmy, więc XmlSerializer na SoapFormatter lub BinaryFormatter i uruchommy przykładowy kod jeszcze raz (można użyć też DataContractSerializer ale z drobną modyfikacją kodu).
Efekt końcowy będzie inny niż dla serializatora XML'owego - po deserializacji właściwość Name będzie miała taką samą wartość jak przed serializacją. To nie jedyna różnica. Jeśli do konstruktora domyślnego dodamy linijkę:
Console.WriteLine("I'm in a constructor");
i popatrzymy na liczbę wypisanych na ekran napisów I'm in a constructor to okaże się, że w przypadku serializatora XML'owego konstruktor domyślny został wywołany dwa razy (przy tworzeniu obiektu oraz przy deserializacji), a w przypadku pozostałych klas tylko jeden raz (przy tworzeniu obiektu).
Należy postawić pytanie, jak w takim razie pozostałe serializatory tworzą obiekty przy deserializacji. Przecież muszą wołać jakiś konstruktor. Otóż nie koniecznie. O ile wiem taki BinaryFormatter, pozostałe serializatory korzystają zapewne z podobnych mechanizmów, używa metody FormatterServices.GetSafeUninitializedObject, która jest odpowiedzialna za zaalokowanie odpowiednio dużego fragmentu pamięci dla obiektu zadanego typu.
Po co to wszystko? Czy nie łatwiej wywołać konstruktor? Początkowo pomyślałem, że to kwestia wydajności. Ale szybki test pokazuje, że użycie FormatterServices.GetSafeUninitializedObject jest troszkę wolniejsze niż utworzenie obiektu za pomocą konstruktora.
Efekt końcowy będzie inny niż dla serializatora XML'owego - po deserializacji właściwość Name będzie miała taką samą wartość jak przed serializacją. To nie jedyna różnica. Jeśli do konstruktora domyślnego dodamy linijkę:
Console.WriteLine("I'm in a constructor");
i popatrzymy na liczbę wypisanych na ekran napisów I'm in a constructor to okaże się, że w przypadku serializatora XML'owego konstruktor domyślny został wywołany dwa razy (przy tworzeniu obiektu oraz przy deserializacji), a w przypadku pozostałych klas tylko jeden raz (przy tworzeniu obiektu).
Należy postawić pytanie, jak w takim razie pozostałe serializatory tworzą obiekty przy deserializacji. Przecież muszą wołać jakiś konstruktor. Otóż nie koniecznie. O ile wiem taki BinaryFormatter, pozostałe serializatory korzystają zapewne z podobnych mechanizmów, używa metody FormatterServices.GetSafeUninitializedObject, która jest odpowiedzialna za zaalokowanie odpowiednio dużego fragmentu pamięci dla obiektu zadanego typu.
Po co to wszystko? Czy nie łatwiej wywołać konstruktor? Początkowo pomyślałem, że to kwestia wydajności. Ale szybki test pokazuje, że użycie FormatterServices.GetSafeUninitializedObject jest troszkę wolniejsze niż utworzenie obiektu za pomocą konstruktora.
var s = new Stopwatch(); s.Start(); for(int i = 0; i < 1000000; ++i) FormatterServices.GetSafeUninitializedObject(typeof(A)); s.Stop(); Console.WriteLine(s.ElapsedMilliseconds); //Na mojej maszynie ~130 ms s.Reset(); s.Start(); for (int i = 0; i < 1000000; ++i) new A(); s.Stop(); Console.WriteLine(s.ElapsedMilliseconds); //Na mojej maszynie ~20 msPo chwili zastanowienia doszedłem do innego wniosku. Otóż, jeśli na proces deserializacji popatrzymy jak na proces "przywracania do życia" już raz utworzonych obiektów, to ponowne wywołanie konstruktora po prostu nie ma sensu albo wręcz jest niewskazane. Macie jakieś inne pomysły? Jak sądzicie czemu jedne serializatory wołają konstruktor, a inne nie?