Oh well it was pretty simple to get something that works for me right now at
least.
Here's the code, very far from perfect. Any suggestions or enhancements are
welcome. For example it doesn't deal with many value types right now, but
does cope with deleting or adding fields to classes between sessions and
renaming classes.
---
public static byte[] Serialize(object graph)
{
MemoryStream ms = new MemoryStream();
BinaryWriter wr = new BinaryWriter(ms, System.Text.Encoding.Unicode);
foreach (FieldInfo fi in graph.GetType().GetFields())
{
if (!fi.IsNotSerialized)
{
// write out the name
wr.Write(fi.Name);
wr.Flush();
long lengthPosition = ms.Position;
// write out a dummy length
wr.Write((Int64)0);
long bookmark = ms.Position;
object val = fi.GetValue(graph);
// for primitive types, call the correctly overloaded Write operation
if (fi.FieldType.IsPrimitive || fi.FieldType==typeof(string))
wr.GetType().InvokeMember("Write", BindingFlags.InvokeMethod, null,
wr, new object[] { val });
else
// for more complex types, write out the serialized version of the
object
wr.Write(Serialize(val));
wr.Flush();
long length = ms.Position - (long)bookmark;
wr.Seek((int)lengthPosition, SeekOrigin.Begin); // jump back to the
bookmark to write out the length
// write out the length
wr.Write(length);
wr.Flush();
wr.Seek(0, SeekOrigin.End); // jump to the end
}
}
ms.Capacity = (int)ms.Position;
return ms.GetBuffer(); // ToArray could be used, but copies the buffer
}
public static void DeSerialize(object instance, byte[] buffer, bool
throwErrors)
{
MemoryStream ms = new MemoryStream(buffer);
BinaryReader reader = new BinaryReader(ms, System.Text.Encoding.Unicode);
while (ms.Position<ms.Length)
{
// read the name
string name = reader.ReadString();
// read the length
long len = reader.ReadInt64();
long bookmark = ms.Position;
try
{
// find the type...
FieldInfo fi = instance.GetType().GetField(name);
if (fi!=null && !fi.IsNotSerialized)
{
// get the type...
string typeName = fi.FieldType.FullName;
if (typeName.StartsWith("System."))
{
// primitive type, we can use the ReadXXX functions.
// first, get the primitive type name...
typeName = typeName.Substring(typeName.IndexOf(".")+1);
// then try to find a matching method in the reader
MethodInfo mi = reader.GetType().GetMethod("Read" + typeName);
if (mi==null) throw new ApplicationException("Unsupported type: " +
fi.FieldType.FullName);
object val = mi.Invoke(reader, new object[0]);
// set the value
fi.SetValue(instance, val);
}
else
{
// locate the object in question...
Type objectType = Type.GetType(typeName);
if (objectType!=null)
{
ConstructorInfo constructor =
objectType.GetConstructor(System.Type.EmptyTypes);
if (constructor==null) throw new ApplicationException("Class '" +
typeName + "' must support parameterless constructor");
// create a new instance
object obj = constructor.Invoke(new Object[0]);
byte[] subObjectBuffer = new byte[len];
// get the serialized data from the stream
reader.Read(subObjectBuffer, 0, (int)len);
// deserialize into the object
DeSerialize(obj, subObjectBuffer, throwErrors);
fi.SetValue(instance, obj);
}
else
{
throw new ApplicationException("The class '" + typeName + "' cannot
be resolved");
}
}
}
}
catch (Exception e)
{
// if there's an exception, throw error otherwise just skip it
if (throwErrors) throw (e);
}
// skip that data according to the length stored, in case the type has
changed
ms.Seek(len + bookmark, SeekOrigin.Begin);
}
}
}