Created
August 25, 2011 10:10
-
-
Save chakrit/1170373 to your computer and use it in GitHub Desktop.
WP7 almost-work binary serializer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
namespace MyApp.Models.Serialization | |
{ | |
// TODO: Support for circular references | |
// TODO: Actually not thread-safe due to requiring the _context | |
// on .Write<T> and .Read<T>() not sure how to properly remove it yet. | |
public class Serializer : ISerializer | |
{ | |
private delegate void TypeWriter(Context ctx); | |
private delegate object TypeReader(Context ctx); | |
private class TypeInfo | |
{ | |
public Type Type; | |
public TypeWriter WriteAction; | |
public TypeReader ReadFunc; | |
} | |
private class Context | |
{ | |
public ISerializer Serializer; | |
public Stream Stream; | |
public BinaryWriter Writer; | |
public BinaryReader Reader; | |
public Type PerceivedType; | |
public object Value; | |
} | |
private IDictionary<Type, TypeInfo> _infos; | |
private Context _context; // remove this stateful var | |
public Serializer() | |
{ | |
_infos = new Dictionary<Type, TypeInfo>(); | |
registerCommonTypes(); | |
} | |
// primary serialization entry point | |
public void Serialize(Stream outgoing, object obj) | |
{ | |
var ctx = new Context { | |
Serializer = this, | |
Writer = new BinaryWriter(outgoing), | |
Reader = null, | |
PerceivedType = obj.GetType(), | |
Value = obj, | |
}; | |
serialize(_context = ctx, obj, obj.GetType()); | |
ctx.Writer.Dispose(); | |
_context = null; | |
} | |
public T Deserialize<T>(Stream incoming) | |
{ | |
var ctx = new Context { | |
Serializer = this, | |
Writer = null, | |
Reader = new BinaryReader(incoming), | |
PerceivedType = typeof(T), | |
Value = null, | |
}; | |
var result = deserialize(_context = ctx, ctx.PerceivedType); | |
ctx.Reader.Dispose(); | |
_context = null; | |
return (T)result; | |
} | |
// object-level serialization interface | |
void ISerializer.Write<T>(T obj) | |
{ serialize(_context, obj, typeof(T)); } | |
T ISerializer.Read<T>() | |
{ return (T)deserialize(_context, typeof(T)); } | |
// internal recursive serialization access points | |
private void serialize(Context ctx, object obj, Type perceivedType) | |
{ | |
var info = getTypeInfo(ctx.PerceivedType = perceivedType); | |
ctx.Value = obj; | |
info.WriteAction.Invoke(ctx); | |
} | |
private object deserialize(Context ctx, Type type) | |
{ | |
var info = getTypeInfo(ctx.PerceivedType = type); | |
return info.ReadFunc.Invoke(ctx); | |
} | |
private void registerCommonTypes() | |
{ | |
// simple value types | |
register(typeof(string), | |
c => | |
{ | |
if (c.Value == null) { // null flag | |
c.Writer.Write(true); | |
} | |
else { | |
c.Writer.Write(false); | |
c.Writer.Write((string)c.Value); | |
} | |
}, c => c.Reader.ReadBoolean() ? // null flag | |
null : | |
c.Reader.ReadString()); | |
register(typeof(int), | |
c => c.Writer.Write((int)c.Value), | |
c => c.Reader.ReadInt32()); | |
register(typeof(long), | |
c => c.Writer.Write((long)c.Value), | |
c => c.Reader.ReadInt64()); | |
register(typeof(bool), | |
c => c.Writer.Write((bool)c.Value), | |
c => c.Reader.ReadBoolean()); | |
register(typeof(DateTime), | |
c => c.Writer.Write(((DateTime)c.Value).Ticks), | |
c => new DateTime(c.Reader.ReadInt64())); | |
} | |
private void registerEnum(Type type) | |
{ | |
register(type, | |
c => c.Writer.Write(c.Value.ToString()), | |
c => Enum.Parse(type, (string)c.Reader.ReadString(), false)); | |
} | |
private void registerEnumerable(Type type) | |
{ | |
var itemType = type.GetGenericArguments()[0]; | |
register(type, | |
c => // writes | |
{ | |
var items = ((IEnumerable)c.Value).Cast<object>(); | |
var count = items.Count(); | |
c.Writer.Write(count); | |
foreach (var item in items) | |
serialize(c, item, itemType); | |
}, c => // reads | |
{ | |
var count = c.Reader.ReadInt32(); | |
var items = Array.CreateInstance(itemType, count); | |
for (var i = 0; i < items.Length; i++) | |
items.SetValue(deserialize(c, itemType), i); | |
return items; | |
}); | |
} | |
private void registerComplex(Type type) | |
{ | |
// pre-extract ctor to speed things up a bit | |
var ctor = type.GetConstructor(new Type[] { typeof(ISerializer) }); | |
if (ctor == null) | |
throw new PopCornException("Cannot serialize type: " + type + | |
" there is no parameterless constructor defined, or I can't access it."); | |
// define writers and readers which use the extracted information | |
register(type, | |
c => // writes | |
{ | |
if (c.Value == null) { | |
c.Writer.Write(true); // null flag | |
} | |
else { | |
c.Writer.Write(false); | |
((ISerializable)c.Value).Write(c.Serializer); | |
} | |
}, c => // reads | |
{ | |
if (c.Reader.ReadBoolean() == true) // null flag check | |
return null; | |
return ctor.Invoke(new object[] { c.Serializer }); | |
}); | |
} | |
private void register(Type type, TypeWriter writer, TypeReader reader) | |
{ | |
_infos.Add(type, new TypeInfo { | |
Type = type, | |
WriteAction = writer, | |
ReadFunc = reader, | |
}); | |
} | |
private TypeInfo getTypeInfo(Type type) | |
{ | |
// TODO: Add Index to speed up search? | |
TypeInfo result; | |
if (_infos.TryGetValue(type, out result)) | |
return result; | |
if (type.IsEnum) | |
registerEnum(type); | |
else if (type.IsGenericType && | |
type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) | |
registerEnumerable(type); | |
else | |
registerComplex(type); | |
return getTypeInfo(type); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment