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
 }
}  

 

 

 

# re: Serializing and having the best of both worlds

Monday, August 08, 2005 12:27 AM by TeunD

So what exactly does this ReflectionSurrogate do that the XmlSerializer cannot? Your code just serializes the properties. If the object implements IDictionary, it will not serialize the indexer property Item, or will it?

# re: Serializing and having the best of both worlds

Monday, August 08, 2005 1:18 AM by Obiwan Jacobi [Macaw]

I think you dont even need a custom surrogate selector. You can use the standard SurrogateSelector class (System.Runtime.Serialization) and use the AddSurrogate method to add an instance of you surrogate for a specific type. If you want to implement your !IsSerialzable logic, just override the GetSurrogtae method on the class..

# re: Serializing and having the best of both worlds

Monday, August 08, 2005 1:23 AM by Olaf Conijn

Is does nothing the XmlSerializer cannot (probably does a whole lot less). The thing it does is extend an IFormatter with something it does not do.

On a complex graph, lets say an Hashtable that contains other Hashtables and Apples the SoapSerializer will take care of serializing the Hashtables. However if the Apples are not marked as serializable, it throws an exception and the whole graph cannot be serialized.

With the SurrogateSelector set, the Apples would be serialized used reflection and the whole graph serialized fine again.