I have some objects that must be serialized:
class Displayable{
string name;
Sprite icon;
}
The icon
field requires custom
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.