Sunday, August 07, 2005 12:19 PM
Olaf Conijn
Serializing and having the best of both worlds
Recently I was stuck on a serialization problem. I needed to serialize both complex structures (which the XmlSerializer could not handle, such as IDictionary’s) from a 3rd party codebase (which the Binary-/SoapSerializer could not handle, because the types weren’t decorated with [Serializable] attributes).
What I needed was a serializer that had both the intelligence to serialize complex structures and do it through reflection.
While I was studying the IFormatter interface, to solve this problem through AOP (inject advice on the serialize method, mix the ISerializable interface into the foreign objects and probably Reflection.Emit my way out of decorating the types with the [Serializable] attribute :o)...) I stumbled into the IFormatter.SurrogateSelector property.
The SurrogateSelector allows you to extend an IFormatter with your own serialization logic, which is called when the IFormatter isn’t able to serialize the object itself. Even though this would have been a cool AOP demo (which I might write anyway, if someone is interested anyway) I decided to read the fucking documentation and within a half hour cooked myself a ReflectionSurrogateSelector.
The ReflectionSurrogateSelector extends any IFormatter with the logic to serialize through reflection, just as the XmlSerializer does.
An example of how to use the ReflectionSurrgateSelector (though not that pretty, SurrogateSelectors should be chained):
SoapFormatter soapFormatter = new SoapFormatter();
soapFormatter.SurrogateSelector = new ReflectionSurrogateSelector();
using(MemoryStream ms = new MemoryStream())
{
soapFormatter.Serialize(ms, _someInstance);
}
And... the code for the ReflectionSurrogateSelector class itself:
public class ReflectionSurrogateSelector: ISurrogateSelector
{
Hashtable _surrogatesByType = new Hashtable();
ISurrogateSelector _nextSelector;
public void ChainSelector(ISurrogateSelector selector)
{
_nextSelector = selector;
}
public ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector)
{
if (_surrogatesByType.ContainsKey(type))
{
selector = this;
return (ISerializationSurrogate) _surrogatesByType[type];
}
if (!type.IsSerializable)
{
System.Diagnostics.Debug.Write(type.Name);
ISerializationSurrogate surrogate = new ReflectionSurogate(type);
_surrogatesByType[type] = surrogate;
selector = this;
return surrogate;
}
selector = _nextSelector;
return null;
}
public ISurrogateSelector GetNextSelector()
{
return _nextSelector;
}
private sealed class ReflectionSurogate :ISerializationSurrogate
{
Type _t;
FieldInfo[] _fs;
PropertyInfo[] _ps;
public ReflectionSurogate(Type t)
{
_t = t;
_fs = _t.GetFields();
_ps = _t.GetProperties();
}
#region ISerializationSurrogate Members
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
foreach(FieldInfo f in _fs)
{
info.AddValue(f.Name, f.GetValue(obj));
}
foreach(PropertyInfo p in _ps)
{
if (p.CanRead && p.CanWrite && p.GetIndexParameters().Length == 0)
{
info.AddValue(p.Name, p.GetValue(obj, new object[0]));
}
}
}
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
foreach(PropertyInfo pi in _ps)
{
object pValue = info.GetValue(pi.Name, pi.PropertyType);
if (pValue != null)
{
pi.SetValue(obj, pValue, new object[0]);
}
}
foreach(FieldInfo fi in _fs)
{
object fValue = info.GetValue(fi.Name, fi.FieldType);
if (fValue != null)
{
fi.SetValue(obj, fValue);
}
}
return obj;
}
#endregion
}
}