Reputation: 714
I have some objects that must be serialized:
class Displayable{
string name;
Sprite icon;
}
The icon
field requires custom serialization since Sprite
s are already serialized (in different files, with their own format, by a game engine) and I only need to store a way to reference them (let's say a string
, being the key inside a Dictionary<string, Sprite>
).
Using BinaryFormatter
I tried implementing ISerializable
and ISerializationSurrogate
but both of these methods create a new instance of the Sprite object on deserialization so they are not suitable for my case. I would like to have the same functionality of ISerializationSurrogate
except I don't want the first parameter in SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
because I need to return an object I already have in memory instead of receiving a new instance from the deserializer and fill it with data.
I also tried external libraries like SharpSerializer but I don't like the constraints of having a public parameterless constructor and it doesn't really let you customize the serialization of special classes. I've read about ProtoBuffers but that doesn't support inheritance and I need it somewhere else.
So my requirements are:
Is there any library doing this?
Otherwise, am I being too picky? How do you usually achieve serialization of references to objects stored somewhere else?
Thank you in advance.
Edit: Here's what I'd like to have
public class SerializableSprite : ISerializationSurrogate
{
public static Dictionary<string, Sprite> sprites;
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) {
Sprite sprite = obj as Sprite;
info.AddValue("spriteKey", sprite.name);
}
// The first parameter in this function is a newly instantiated Sprite, which I don't need
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) {
return sprites[info.GetString("spriteKey")];
}
}
Upvotes: 1
Views: 1238
Reputation: 117230
In order to prevent BinaryFormatter
from creating a new instance of Sprite
during deserialization, during serialization you can call SerializationInfo.SetType(Type)
to specify alternate type information -- typically some proxy type -- to insert into the serialization stream. During deserialization SetObjectData()
will be passed an instance of the proxy rather than the "real" type to initialize. This proxy type must in turn implement IObjectReference
so that the "real" object can eventually be inserted into the object graph, specifically by looking it up in your table of sprites.
The following does this:
class ObjectReferenceProxy<T> : IObjectReference
{
public T RealObject { get; set; }
#region IObjectReference Members
object IObjectReference.GetRealObject(StreamingContext context)
{
return RealObject;
}
#endregion
}
public sealed class SpriteSurrogate : ObjectLookupSurrogate<string, Sprite>
{
static Dictionary<string, Sprite> sprites = new Dictionary<string, Sprite>();
static Dictionary<Sprite, string> spriteNames = new Dictionary<Sprite, string>();
public static void AddSprite(string name, Sprite sprite)
{
if (name == null || sprite == null)
throw new ArgumentNullException();
sprites.Add(name, sprite);
spriteNames.Add(sprite, name);
}
public static IEnumerable<Sprite> Sprites
{
get
{
return sprites.Values;
}
}
protected override string GetId(Sprite realObject)
{
if (realObject == null)
return null;
return spriteNames[realObject];
}
protected override Sprite GetRealObject(string id)
{
if (id == null)
return null;
return sprites[id];
}
}
public abstract class ObjectLookupSurrogate<TId, TRealObject> : ISerializationSurrogate where TRealObject : class
{
public void Register(SurrogateSelector selector)
{
foreach (var type in Types)
selector.AddSurrogate(type, new StreamingContext(StreamingContextStates.All), this);
}
IEnumerable<Type> Types
{
get
{
yield return typeof(TRealObject);
yield return typeof(ObjectReferenceProxy<TRealObject>);
}
}
protected abstract TId GetId(TRealObject realObject);
protected abstract TRealObject GetRealObject(TId id);
#region ISerializationSurrogate Members
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
var original = (TRealObject)obj;
var id = GetId(original);
info.AddValue("id", id);
// use Info.SetType() to force the serializer to construct an object of type ObjectReferenceWrapper<TRealObject> during deserialization.
info.SetType(typeof(ObjectReferenceProxy<TRealObject>));
}
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
// Having constructed an object of type ObjectReferenceWrapper<TRealObject>,
// look up the real sprite using the id in the serialization stream.
var wrapper = (ObjectReferenceProxy<TRealObject>)obj;
var id = (TId)info.GetValue("id", typeof(TId));
wrapper.RealObject = GetRealObject(id);
return wrapper;
}
#endregion
}
Then apply it to a BinaryFormatter
as follows:
var selector = new SurrogateSelector();
var spriteSurrogate = new SpriteSurrogate();
spriteSurrogate.Register(selector);
BinaryFormatter binaryFormatter = new BinaryFormatter(selector, new StreamingContext());
Sample fiddle.
Update
While the code above works in .Net 3.5 and above, it apparently does not work in unity3d despite compiling successfully there because of some problem with IObjectReference
. The following also works in .Net 3.5 and above and also avoids the use of IObjectReference
by returning the real object from ISerializationSurrogate.SetObjectData()
. Thus it should work in unity3d as well (confirmed in comments):
public sealed class SpriteSurrogate : ObjectLookupSurrogate<string, Sprite>
{
static Dictionary<string, Sprite> sprites = new Dictionary<string, Sprite>();
static Dictionary<Sprite, string> spriteNames = new Dictionary<Sprite, string>();
public static void AddSprite(string name, Sprite sprite)
{
if (name == null || sprite == null)
throw new ArgumentNullException();
sprites.Add(name, sprite);
spriteNames.Add(sprite, name);
}
public static IEnumerable<Sprite> Sprites
{
get
{
return sprites.Values;
}
}
protected override string GetId(Sprite realObject)
{
if (realObject == null)
return null;
return spriteNames[realObject];
}
protected override Sprite GetRealObject(string id)
{
if (id == null)
return null;
return sprites[id];
}
}
public abstract class ObjectLookupSurrogate<TId, TRealObject> : ISerializationSurrogate where TRealObject : class
{
class SurrogatePlaceholder
{
}
public void Register(SurrogateSelector selector)
{
foreach (var type in Types)
selector.AddSurrogate(type, new StreamingContext(StreamingContextStates.All), this);
}
IEnumerable<Type> Types
{
get
{
yield return typeof(TRealObject);
yield return typeof(SurrogatePlaceholder);
}
}
protected abstract TId GetId(TRealObject realObject);
protected abstract TRealObject GetRealObject(TId id);
#region ISerializationSurrogate Members
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
var original = (TRealObject)obj;
var id = GetId(original);
info.AddValue("id", id);
// use Info.SetType() to force the serializer to construct an object of type SurrogatePlaceholder during deserialization.
info.SetType(typeof(SurrogatePlaceholder));
}
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
// Having constructed an object of type SurrogatePlaceholder,
// look up the real sprite using the id in the serialization stream.
var id = (TId)info.GetValue("id", typeof(TId));
return GetRealObject(id);
}
#endregion
}
Sample fiddle #2.
Upvotes: 4