2012-12-28 19 views
5

Konwertuję grę Java na C# (Ataki tytanów przez Puppy Games) i jestem prawie zajęta teraz, oprócz ostatniego zadania, które jest serializacją stanu gry dla plików zapisu.Serializacja serialu - mieszanie [Serializable] z niestandardowym drzewem dziedziczenia

Typowe hierarchia: zasób (zasada) -> Feature-> Ekran/Działanie/Entity-> GameScreen/LaserEffect/Invader

kod Java wykorzystuje standardowe ObjectOutputStream/ObjectInputStream do wykonania serializacji binarnej, ale nieznośno wykonuje pewne readResolve/writeResolve działa na poziomie klasy podstawowej (Zasób), aby dostosować proces serializacji (jeśli zasób jest nazwany, nie przekształca go do postaci szeregowej i po prostu zwraca serwer proxy o nazwie używanej później do pobrania zasobu z mapy mieszającej).

moje naiwne rozwiązania jest ślepo kopiować to podejście i wdrożyć ISerializable w klasie bazowej przesłonić typu ...

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)); 
} 

Q) Tak, jestem pewien, że wszystkie zakłady są wyłączone do zabudowy -w serializacji i muszę zaimplementować ISerializable do końca łańcucha dziedziczenia wraz z konstruktorem serializacji?

Uwaga Klasa GetObjectData jest wirtualna, więc klasy pochodne mogą serializować ich pola, a następnie wywoływać klasę podstawową. Działa to, ale jest to mnóstwo żmudnej pracy dodając do wielu klas (100).

Niektóre typy pochodne (Sprite, InvaderBehavour, itp.) Również wykonują niestandardową pracę serializacyjną, aby pogorszyć sprawę.

Przyjrzałem się artykułom Jeffreya Richtera na ten temat i próbowałem użyć konstruktu typu ResourceSurrogateSelector: ISerializationSurrogate, ale te metody serializacji są wywoływane tylko wtedy, gdy typ serializowany jest zasobem, a nie typem pochodzącym z zasobu (np. nie zostanie wywołana serializowanie Invadera lub GameScreen)

Q) Czy istnieje inteligentny sposób na zrobienie tego?

Udało mi się utrzymać dwie bazy kodowe bardzo blisko siebie, co znacznie ułatwiło konwersję - chciałbym kontynuować to podejście tutaj (więc nie ma XmlSerializer, Protobuf, itp.), Chyba że jest naprawdę przekonujący powód, by tego nie robić.

Zastanawiam się nad napisaniem Java, aby zautomatyzować proces i odzwierciedlić typy implementujące interfejs Serializable i utworzyć plik .cs ze wszystkimi kodami serializacji .Net, aby nie zanieczyszczać plików głównej klasy (Zrobiłbym je częściowe)

PS - Docelowymi platformami będą Windows8/Surface/XBox360 po stronie .Net (więc wersja 4) i prawdopodobnie PS Vita/może iOS używający Mono. Zapisane dane są deserializowane na platformie, na której zostały serializowane.

EDIT Odpowiedź przez Sergey Teplyakov w tym poście .... .NET, C#: How to add a custom serialization attribute that acts as ISerializable interface ... doprowadziły mnie do interfejsu ISurrogateSelector która wygląda jak to pomoże w wyborze odpowiednich klas pochodnych.

Odpowiedz

1

To jest to, co udało mi się wymyślić do tej pory i jestem całkiem zadowolony z tego :-) Właśnie muszę dodać readResolve/writeReplace i jestem o zrobieniu! (Prawdopodobnie zawinę obiekt Object, SerializationInfo, StreamingContext w górę w ObjectOutputStream dla dobrego pomiaru).

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)); 
      } 
     } 
    } 
} 
}