2013-08-22 9 views
13

Używam prostej klasy, która jest serializowalna. Ma konstruktora do deserializacji:.NET Deserializacja z OnDeserializing i OnDeserialized

protected MyClass(SerializationInfo info, StreamingContext context) 

i metody GetObjectData do serializacji. To działa dobrze.

Teraz dodałem dwie metody monitorowania deserialisation:

 [OnDeserializing()] 
    internal void OnDeserializingMethod(StreamingContext context) 
    { 
     System.Diagnostics.Trace.WriteLine("OnDeserializingMethod: " + this.GetType().ToString()); 
    } 

    [OnDeserialized()] 
    internal void OnDeserializedMethod(StreamingContext context) 
    { 
     System.Diagnostics.Trace.WriteLine("OnDeserializedMethod: " + this.GetType().ToString()); 
    } 

i zastanawiałem się, w jakiej kolejności te metody są beeing nazywa. Teraz obie metody są wywoływane przed wywołaniem konstruktora. Jak to możliwe i dlaczego nie jest wywoływana metoda "OnDeserialized" po wywołaniu konstruktora (deserialization-)? Jak można wywołać metodę (niestatyczną) przed wykonaniem dowolnego konstruktora? (Podaję się BinaryFormatter)

Odpowiedz

19

Teraz obie metody sprawdzony przed konstruktor zostanie wywołany

Nie, kolejność jest:

  • OnDeserializingMethod
  • .ctor
  • OnDeserializedMethod

Jak można wywołać metodę (niestatyczną) przed wykonaniem dowolnego konstruktora?

Ponieważ oszukuje i kłamie; nie tworzy obiektu z konstruktorem; nie naprawdę. Używa FormatterServices.GetUninitializedObject do przydzielenia pustej przestrzeni wanilii. A następnie, jeśli istnieje niestandardowy konstruktor deserializacji, wywołuje konstruktora nad tym obiektem. Paskudny. Tak, w zasadzie:

var obj = FormatterServices.GetUninitializedObject(typeof(MyClass)); 
var ctor = obj.GetType().GetConstructor(
    BindingFlags.Instance | BindingFlags.Public| BindingFlags.NonPublic, 
    null, 
    new[] { typeof(SerializationInfo), typeof(StreamingContext) }, 
    null); 
ctor.Invoke(obj, new object[2]); 

IMO pewnie powinien był ten drugi sposób na interfejsie, ale niezależnie od przyczyny ISerializable: nie.Wstyd naprawdę: to uczyniłoby to bardziej uczciwym i uniknęło ludzi, którzy musieliby pamiętać o implementacji niestandardowego konstruktora.

Przykład Wydajność:

.ctor: MyClass 
> serializing 
OnSerializingMethod: MyClass 
GetObjectData: MyClass 
OnSerializedMethod: MyClass 
< serializing 
> deserializing 
OnDeserializingMethod: MyClass 
.ctor: MyClass 
OnDeserializedMethod: MyClass 
< deserializing 

Przykład Kod:

using System; 
using System.IO; 
using System.Runtime.CompilerServices; 
using System.Runtime.Serialization; 
using System.Runtime.Serialization.Formatters.Binary; 
[Serializable] 
class MyClass : ISerializable 
{ 
    public MyClass() { Trace(); } 
    protected MyClass(SerializationInfo info, StreamingContext context) { Trace(); } 
    public void GetObjectData(SerializationInfo info, StreamingContext context) { Trace(); } 
    void Trace([CallerMemberName] string caller = null) 
    { 
     System.Console.WriteLine("{0}: {1}", caller, GetType().Name); 
    } 
    [OnDeserializing()] 
    internal void OnDeserializingMethod(StreamingContext context) { Trace(); } 

    [OnDeserialized()] 
    internal void OnDeserializedMethod(StreamingContext context) { Trace(); } 

    [OnSerializing()] 
    internal void OnSerializingMethod(StreamingContext context) { Trace(); } 

    [OnSerialized()] 
    internal void OnSerializedMethod(StreamingContext context) { Trace(); } 

    static void Main() 
    { 
     using (var ms = new MemoryStream()) 
     { 
      var orig = new MyClass(); 
      var ser = new BinaryFormatter(); 
      System.Console.WriteLine("> serializing"); 
      ser.Serialize(ms, orig); 
      System.Console.WriteLine("< serializing"); 
      ms.Position = 0; 
      System.Console.WriteLine("> deserializing"); 
      ser.Deserialize(ms); 
      System.Console.WriteLine("< deserializing"); 
     } 
    } 
} 
+0

ten kod wywoła obiekt constuctor var ctor = obj.GetType(). GetConstructor (.... Użyłbym InvokeMember do wywołania metody http://www.codeproject.com/Articles/19911/Dynamically-Invoke-A -Method-Given-Strings-with-Met –

+0

Dzięki, perfekcyjna odpowiedź, oznaczy ją jako zaakceptowaną po tym, jak dowiedziałem się, dlaczego w moim scenariuszu ctor zostaje wywołany jako ostatni. Może dlatego, że obiekt nie jest korzeniem zserializowanego drzewa – Gerhard

+0

OK Marc, twoja odpowiedź jest poprawna tylko dla obiektu root.Zobacz moją odpowiedź poniżej dla bardziej złożonego scenariusza.Wszelkie powody, dlaczego zachowuje się w ten sposób? Nawiasem mówiąc: bardzo fajne [CallerMemberName] Atrybut! Nie wiedziałem, że wcześniej (ale tylko .NET 4.5) – Gerhard

1

[OnDeserializing] wskazuje sposób nazywać przed deserializacji [OnDeserialized] wskazuje sposób być nazywane po prostu po deserializacjia

w [OnDeserializing] czynności metody jako pseudoconstructor do deserializacji i jest przydatna dla inicjalizacji pól wyłączonych z serializacji:

[OnDeserializing] i [OnDeserialized] deserializacji omija wszystkie swoje normalne konstruktorów oraz inicjatorów polowych. Jest to mało znaczące, jeśli każde pole uczestniczy w serializacji, ale może to być problematyczne, jeśli niektóre pola są wykluczone przez [NiesSerialized].

Ja wziąłem ten tekst z książki Albahari C# 5.0 w skróconej stronie 713 sprawdź online wiele przykładów i opis twojego problemu.

Dzięki

+0

Chociaż są to wszystko prawda, to nie jest dla mnie oczywiste, że odpowiedzi na pytania zadawane –

+1

Wystarczy wywołać odpowiednią metodę na OnDeserializingMethod! –

3

Kolejność połączenia zależy od pogoda przedmiotem jest pierwiastkiem odcinkach drzewa lub jakiś element obiektu, który jest również serializowane na tym samym wykresie obiektów. Mam następujący wynik z rozszerzonego przykład dostarczonych przez Marc Gravell:

SerRoot.ctor 
SerMember.ctor 
> serializing 
SerRoot.OnSerializingMethod 
GetObjectData 
SerMember.OnSerializingMethod 
SerMember.GetObjectData 
SerRoot.OnSerializedMethod 
SerMember.OnSerializedMethod 
< serializing 
> deserializing 
SerRoot.OnDeserializingMethod 
SerMember.OnDeserializingMethod 
SerMember.OnDeserializedMethod 
SerMember.ctor(info, context) 
SerRoot.ctor(info, context) 
SerRoot.OnDeserializedMethod 
< deserializing 

Zauważ, że w deserializacji SerMember.ctor nazywa po SerMember.OnDeserializedMethod! Jest to kod:

 static void Main(string[] args) 
    { 
     using (var ms = new MemoryStream()) 
     { 
      var orig = new SerRoot(); 
      var ser = new BinaryFormatter(); 
      System.Console.WriteLine("> serializing"); 
      ser.Serialize(ms, orig); 
      System.Console.WriteLine("< serializing"); 
      ms.Position = 0; 
      System.Console.WriteLine("> deserializing"); 
      ser.Deserialize(ms); 
      System.Console.WriteLine("< deserializing"); 
     } 
    } 
[Serializable] 
class SerRoot : ISerializable 
{ 
    public SerMember m; 
    public SerRoot() 
    { 
     System.Console.WriteLine("SerRoot.ctor"); 
     m = new SerMember(); 
    } 
    protected SerRoot(SerializationInfo info, StreamingContext context) 
    { 
     System.Console.WriteLine("SerRoot.ctor(info, context)"); 
     m = info.GetValue("m", typeof(SerMember)) as SerMember; 
    } 
    public void GetObjectData(SerializationInfo info, StreamingContext context) 
    { 
     System.Console.WriteLine("GetObjectData"); 
     info.AddValue("m", m); 
    } 
    [OnDeserializing()] 
    internal void OnDeserializingMethod(StreamingContext context) { System.Console.WriteLine("SerRoot.OnDeserializingMethod"); } 

    [OnDeserialized()] 
    internal void OnDeserializedMethod(StreamingContext context) { System.Console.WriteLine("SerRoot.OnDeserializedMethod"); } 

    [OnSerializing()] 
    internal void OnSerializingMethod(StreamingContext context) { System.Console.WriteLine("SerRoot.OnSerializingMethod"); } 

    [OnSerialized()] 
    internal void OnSerializedMethod(StreamingContext context) { System.Console.WriteLine("SerRoot.OnSerializedMethod"); } 

} 
[Serializable] 
class SerMember : ISerializable 
{ 
    string text; 
    public SerMember() 
    { 
     System.Console.WriteLine("SerMember.ctor"); 
     text = "test"; 
    } 
    protected SerMember(SerializationInfo info, StreamingContext context) 
    { 
     System.Console.WriteLine("SerMember.ctor(info, context)"); 
     text = info.GetString("text"); 
    } 
    public void GetObjectData(SerializationInfo info, StreamingContext context) 
    { 
     System.Console.WriteLine("SerMember.GetObjectData"); 
     info.AddValue("text", text); 
    } 
    [OnDeserializing()] 
    internal void OnDeserializingMethod(StreamingContext context) { System.Console.WriteLine("SerMember.OnDeserializingMethod"); } 

    [OnDeserialized()] 
    internal void OnDeserializedMethod(StreamingContext context) { System.Console.WriteLine("SerMember.OnDeserializedMethod"); } 

    [OnSerializing()] 
    internal void OnSerializingMethod(StreamingContext context) { System.Console.WriteLine("SerMember.OnSerializingMethod"); } 

    [OnSerialized()] 
    internal void OnSerializedMethod(StreamingContext context) { System.Console.WriteLine("SerMember.OnSerializedMethod"); } 

} 
+0

To jest ... intrygujące, widzę to, co widzisz, i mam ochotę dojść do wniosku, że wygląda okropnie jak błąd ... Zgadzam się, że kolejność wygląda na walnięcie, ale: nie widzę, żeby się zmieniło. Dobrą wiadomością jest to, że jeśli impl ement 'IDeserializationCallback', który zostanie wywołany we właściwym czasie. –

+1

Ten "błąd" sprawia mi wiele przykrości. Dobry połów. @Marc: Używanie IDeserializationCallback nie zawsze jest rozwiązaniem. Niektóre serializacje nie obsługują go, ponieważ nie jest przenośny (np. Json.NET, [patrz tutaj] (https://github.com/JamesNK/Newtonsoft.Json/pull/421)). Wygląda na to, że nie ma innego rozwiązania niż unikanie obiektów ISerializowalnych poza obiektem głównym na wykresie. –

Powiązane problemy