I’m taking some time off from not blogging to do a little blogging. I hope this doesn’t inconvenience absolutely nobody.
I was doing some [binary] serialization work recently when I came across a problem – I wanted to serialize objects with delegate fields that were populated with anonymous methods at runtime. To wit, I had types like this:
public delegate void MakeMove(); public class AdrianPeterson { public int GameOneYards { get; set; } public Football Ball { get; set; } public MakeMove Move { get; set; } }
Populated like this:
var explicitDirections = new List<string> { "left", "right", "left" }; var ap = new AdrianPeterson(); var apName = ap.GetType().Name; ap.GameOneYards = 87; ap.Move = () => Moves.Weave(apName, explicitDirections);
So, I pop a [Serializable] on AdrianPeterson (and Football), and I’m set, right? Wrong. Wrong like getting away from running AP in the second half when the only receiving threat you have is being blanketed by the Saints defensive backfield. You end up with an exception like this:
System.Runtime.Serialization.SerializationException: Type 'BlahBlahBlah+<>c__DisplayClass2' in Assembly 'YadaYadaYada' is not marked as serializable.
Ah, of course, the backing field for the ‘Move’ property holds an instance to an object with a runtime type of ‘BlahBlahBlah+<>c__DisplayClass2’ – the compiler-generated class that encapsulates the anonymous method and its scope (or something like that… let’s not get too technical). That class isn’t marked as serializable. Hence the exception. Crap!
No problem, a quick google search gave me this code as the first result. It works very well, except it doesn’t handle fields declared as abstract types. I made a little modification, then I started to get the dreaded refactoring itch. I started looking at the if/else if/else mess that was mirrored in both the deserialization constructor and ‘GetObjectData’ method and thought “clearly these are strategies determined by the type”. And I thought a little more, “this looks like a good place for a chain of responsibility or something like that.” And I thought a little more, and I realized that the serialization architecture in the .NET framework is built to handle this exact situation!
All I really had to do was handle serialization of classes not marked with the aforementioned attribute, and the rest fell into place. Relevant classes:
public class UnattributedTypeSurrogate : ISerializationSurrogate { private const BindingFlags publicOrNonPublicInstanceFields = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) { var type = obj.GetType(); foreach (var field in type.GetFields(publicOrNonPublicInstanceFields)) { var fieldValue = field.GetValue(obj); var fieldValueIsNull = fieldValue != null; if (fieldValueIsNull) { var fieldValueRuntimeType = fieldValue.GetType(); info.AddValue(field.Name + "RuntimeType", fieldValueRuntimeType.AssemblyQualifiedName); } info.AddValue(field.Name + "ValueIsNull", fieldValueIsNull); info.AddValue(field.Name, fieldValue); } } public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) { var type = obj.GetType(); foreach (var field in type.GetFields(publicOrNonPublicInstanceFields)) { var fieldValueIsSerializable = info.GetBoolean(field.Name + "ValueIsNull"); if (fieldValueIsSerializable) { var fieldValueRuntimeType = info.GetString(field.Name + "RuntimeType"); field.SetValue(obj, info.GetValue(field.Name, Type.GetType(fieldValueRuntimeType))); } } return obj; } } public class UnattributedTypeSurrogateSelector : ISurrogateSelector { private readonly SurrogateSelector innerSelector = new SurrogateSelector(); private readonly Type iFormatter = typeof (IFormatter); public void ChainSelector(ISurrogateSelector selector) { innerSelector.ChainSelector(selector); } public ISerializationSurrogate GetSurrogate( Type type, StreamingContext context, out ISurrogateSelector selector) { if (!type.IsSerializable) { selector = this; return new UnattributedTypeSurrogate(); } return innerSelector.GetSurrogate(type, context, out selector); } public ISurrogateSelector GetNextSelector() { return innerSelector.GetNextSelector(); } }
var formatter = new BinaryFormatter(); formatter.SurrogateSelector = new UnattributedTypeSurrogateSelector(); formatter.Serialize(serializedAP, adrianPeterson);
This hasn’t been rigorously tested, and there is at least one case where I know it will fail – if the formatter that is doing the serialization gets captured in the compiler-generated class for the anonymous method (which can easily happen if the formatter is a field in the outer class and the anon. method uses another field from that class).
Full code, with tests, can be found here.
Comments