2012-12-28 10 views
5

Sto convertendo un gioco Java in C# (Titan Attacks di Puppy Games) e sono praticamente finito ora a parte l'ultimo compito che è la serializzazione dello stato del gioco per salvare i file.. Serializzazione net - mixing [Serializable] con albero di ereditarietà personalizzato

gerarchia tipica: Resource (base) -> Feature-> Schermo/Effetto/entity> GameScreen/LaserEffect/Invader

Il codice Java utilizza lo standard ObjectOutputStream/ObjectInputStream per eseguire la serializzazione binaria ma esegue fastidiosamente alcuni readResolve/writeResolve funziona a livello di classe base (Risorsa) per personalizzare il processo di serializzazione (se una risorsa è denominata, non la serializza e semplicemente restituisce un proxy con un nome che viene utilizzato in seguito per estrarre la risorsa da una hashmap).

mie soluzioni ingenuo è quello di copiare ciecamente questo approccio e implementare ISerializable nella classe di base per ignorare l'TYPE ...

public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { 
    if (name != null) { 
     // Got a name, so send a SerializedResource that just references us 
     info.AddValue("name", this.name); 
     info.SetType(typeof(SerializedResource)); 
     return; 
    } 

    //Serialize just MY fields (defined in Resource) 
    this.SerializeMyFields(info, context, typeof(Resource)); 
} 

D) Quindi, sono abbastanza sicuro che tutte le scommesse sono spenti per built -in serializzazione e devo implementare ISerializable fino in fondo la catena di ereditarietà con il costruttore di serializzazione?

Nota GetObjectData è virtuale in modo che le classi derivate possano serializzare i loro campi e quindi chiamare la classe base. Questo funziona, ma è un sacco di lavoro noioso che aggiunge a molte classi (100).

Alcuni tipi derivati ​​(Sprite, InvaderBehaviour, ecc.) Eseguono anche operazioni di serializzazione personalizzate per peggiorare le cose.

Ho esaminato gli articoli di Jeffrey Richter sull'argomento e ho provato a utilizzare un costrutto di tipo ResourceSurrogateSelector: ISerializationSurrogate, ma questi metodi di serializzazione vengono richiamati solo se il tipo serializzato è una risorsa e non un tipo derivato da una risorsa (ad es. non viene chiamato serializzare un Invader o GameScreen)

Q) c'è un modo intelligente per farlo?

Sono riuscito a mantenere le due basi di codice molto vicine tra loro e questo ha reso la conversione molto più semplice - Mi piacerebbe continuare questo approccio qui (quindi senza XmlSerializer, Protobuf, ecc.) A meno che non ci sia un ragione molto convincente per non farlo.

Ho pensato di scrivere un po 'di Java per automatizzare il processo e riflettere i tipi che implementano l'interfaccia Serializable e creare un file .cs con tutto il codice di serializzazione .Net in modo da non inquinare i file di classe principale (Li renderei parziali)

PS - Le piattaforme di destinazione saranno Windows8/Surface/XBox360 sul lato .Net delle cose (quindi versione 4) e probabilmente PS Vita/forse iOS che utilizza Mono. I salvataggi sono deserializzati sulla piattaforma su cui sono stati serializzati.

EDIT Una risposta da Sergey Teplyakov in questo post .... .NET, C#: How to add a custom serialization attribute that acts as ISerializable interface ... mi ha portato all'interfaccia ISurrogateSelector che sembra che vi aiuterà con la selezione delle classi derivate desiderati.

risposta

1

Questo è quello che sono riuscito a fare finora e sono abbastanza contento :-) Devo solo aggiungere readResolve/writeReplace e ho quasi finito! (Probabilmente avvolgere Object, SerializationInfo, StreamingContext si presenta in un ObjectOutputStream anche per buona misura).

using System; 
using System.Diagnostics; 
using System.IO; 
using System.Reflection; 
using System.Runtime.Serialization; 
using System.Runtime.Serialization.Formatters.Binary; 

using java.io; //My implementation of various Java classes 

namespace NewSerializationTest { 

public sealed class JavaSerializableSurrogateSelector : ISurrogateSelector 
{ 
    public void ChainSelector(ISurrogateSelector selector) { throw new NotImplementedException(); } 

    public ISurrogateSelector GetNextSelector() { throw new NotImplementedException(); } 

    public ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector) 
    { 
     if (typeof(Serializable).IsAssignableFrom(type)) 
     { 
      selector = this; 
      return new JavaSerializationSurrogate(); 
     } 

     //Type is not marked (java.io.)Serializable 
     selector = null; 
     return null; 
    } 
} 

public sealed class JavaSerializationSurrogate : ISerializationSurrogate { 

    //Method called to serialize a java.io.Serializable object 
    public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context) { 

     //Do the entire tree looking for the 'marker' methods 
     var type = obj.GetType(); 
     while (type != null) 
     { 
      var writeObject = type.GetMethod("writeObject", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(SerializationInfo), typeof(StreamingContext), typeof(Type) }, null); 
      if (writeObject != null) { 
       //Class has declared custom serialization so call through to that 
       writeObject.Invoke(obj, new object[] { info, context, type }); 
      } else { 
       //Default serialization of all non-transient fields at this level only (not the entire tree) 
       obj.SerializeFields(info, context, type); 
      } 

      type = type.BaseType; 
     } 
    } 

    //Method called to deserialize a java.io.Serializable object 
    public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) { 

     //Do the entire tree looking for the 'marker' methods 
     var type = obj.GetType(); 
     while (type != null) 
     { 
      var readObject = type.GetMethod("readObject", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(SerializationInfo), typeof(StreamingContext), typeof(Type) }, null); 
      if (readObject != null) { 
       //Class has declared custom serialization so call through to that 
       readObject.Invoke(obj, new object[] { info, context, type }); 
      } else { 
       //Default serialization of all non-transient fields at this level only (not the entire tree) 
       obj.DeserializeFields(info, context, type); 
      } 

      type = type.BaseType; 
     } 

     return null; 
    } 
} 

[Serializable] 
class A : java.io.Serializable { 
    public string Field1; 
} 

[Serializable] 
class B : A { 
    public string Field2; 

    private void readObject(SerializationInfo stream, StreamingContext context, Type declaringType) { 
     stream.defaultReadObject(context, this, declaringType); 

     Debug.WriteLine("B: readObject"); 
    } 

    private void writeObject(SerializationInfo stream, StreamingContext context, Type declaringType) { 
     stream.defaultWriteObject(context, this, declaringType); 

     Debug.WriteLine("B: writeObject"); 
    } 
} 

[Serializable] 
class C: B { 
    public string Field3; 

    private void writeObject(SerializationInfo stream, StreamingContext context, Type declaringType) { 
     stream.defaultWriteObject(context, this, declaringType); 

     Debug.WriteLine("C: writeObject"); 
    } 
} 

public static class SerializationInfoExtensions { 

    public static void defaultWriteObject(this SerializationInfo info, StreamingContext context, object o, Type declaringType) { 
     o.SerializeFields(info, context, declaringType); 
    } 

    public static void defaultReadObject(this SerializationInfo info, StreamingContext context, object o, Type declaringType) { 
     o.DeserializeFields(info, context, declaringType); 
    } 
} 

class Program { 
    static void Main(string[] args) { 

     var myC = new C { Field1 = "tom", Field2 = "dick", Field3 = "harry" }; 

     using (var ms = new MemoryStream()) { 
      var binaryFormatter = new BinaryFormatter(); 
      binaryFormatter.SurrogateSelector = new JavaSerializableSurrogateSelector(); 

      binaryFormatter.Serialize(ms, myC); 
      ms.Position = 0; 
      var myCDeserialized = binaryFormatter.Deserialize(ms); 
     } 
    } 
} 

/// <summary> 
/// Extensions to the object class. 
/// </summary> 
public static class ObjectExtensions 
{ 
    /// <summary> 
    /// Serializes an object's class fields. 
    /// </summary> 
    /// <param name="source">The source object to serialize.</param> 
    /// <param name="info">SerializationInfo.</param> 
    /// <param name="context">StreamingContext.</param> 
    /// <param name="declaringType">The level in the inheritance whose fields are to be serialized - pass null to serialize the entire tree.</param> 
    public static void SerializeFields(this object source, SerializationInfo info, StreamingContext context, Type declaringType) 
    { 
     //Serialize the entire inheritance tree if there is no declaringType passed. 
     var serializeTree = declaringType == null; 

     //Set the level in the class heirarchy we are interested in - if there is no declaring type use the source type (and the entire tree will be done). 
     var targetType = declaringType ?? source.GetType(); 

     //Get the set of serializable members for the target type 
     var memberInfos = FormatterServices.GetSerializableMembers(targetType, context); 

     // Serialize the base class's fields to the info object 
     foreach (var mi in memberInfos) 
     { 
      if (serializeTree || mi.DeclaringType == targetType) { 
       //Specify the name to use as the key - if the entire tree is being done then the names will already have a prefix. Otherwise, we need to 
       //append the name of the declaring type. 
       var name = serializeTree ? mi.Name : mi.DeclaringType.Name + "$" + mi.Name; 

       info.AddValue(name, ((FieldInfo)mi).GetValue(source)); 
      } 
     } 
    } 

    /// <summary> 
    /// Deserializes an object's fields. 
    /// </summary> 
    /// <param name="source">The source object to serialize.</param> 
    /// <param name="info">SerializationInfo.</param> 
    /// <param name="context">StreamingContext.</param> 
    /// <param name="declaringType">The level in the inheritance whose fields are to be deserialized - pass null to deserialize the entire tree.</param> 
    public static void DeserializeFields(this object source, SerializationInfo info, StreamingContext context, Type declaringType) 
    { 
     //Deserialize the entire inheritance tree if there is no declaringType passed. 
     var deserializeTree = declaringType == null; 

     //Set the level in the class heirarchy we are interested in - if there is no declaring type use the source type (and the entire tree will be done). 
     var targetType = declaringType ?? source.GetType(); 

     var memberInfos = FormatterServices.GetSerializableMembers(targetType, context); 

     // Deserialize the base class's fields from the info object 
     foreach (var mi in memberInfos) 
     { 
      //Only serialize the fields at the specific level requested. 
      if (deserializeTree || mi.DeclaringType == declaringType) 
      { 
       // To ease coding, treat the member as a FieldInfo object 
       var fi = (FieldInfo) mi; 

       //Specify the name to use as the key - if the entire tree is being done then the names will already have a prefix. Otherwise, we need to 
       //append the name of the declaring type. 
       var name = deserializeTree ? mi.Name : mi.DeclaringType.Name + "$" + mi.Name; 

       // Set the field to the deserialized value 
       fi.SetValue(source, info.GetValue(name, fi.FieldType)); 
      } 
     } 
    } 
} 
} 
Problemi correlati