ISerializationSurrogate interface
I needed to serialize a C# iterator. The obstacle is that the C# compiler does not mark its generated iterator classes with [Serializable]. The same applies to the closure classes generated for lambdas and anonymous methods. The corresponding F# types do not have this restriction:
let xs = seq { yield 1 }
xs.GetType().IsSerializable // true
id.GetType().IsSerializable // true
One way to handle this is to use a serialization surrogate, which supplies the serialization and deserialization logic for a type that does not provide it itself. A simple implementation uses reflection to enumerate the object’s fields and store their values in SerializationInfo. Since the compiler-generated iterator type cannot be referenced directly in source code, reflection provides a way to inspect its state:
using System;
using System.Reflection;
using System.Runtime.Serialization;
sealed class AnySurrogate : ISerializationSurrogate
{
private const BindingFlags AllFields =
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
Type objType = obj.GetType();
foreach (var field in objType.GetFields(AllFields))
{
info.AddValue(field.Name, field.GetValue(obj), field.FieldType);
}
}
public object SetObjectData(
object obj,
SerializationInfo info,
StreamingContext context,
ISurrogateSelector selector)
{
Type objType = obj.GetType();
foreach (var serializedValue in info)
{
var field = objType.GetField(serializedValue.Name, AllFields);
if (field == null)
{
throw new SerializationException(string.Format(
"Field '{0}' was not found in the target type.", serializedValue.Name));
}
if (field.FieldType != serializedValue.ObjectType)
{
throw new SerializationException(string.Format(
"Serialized field '{0}' has type '{1}', but the target field has type '{2}'.",
serializedValue.Name, serializedValue.ObjectType, field.FieldType));
}
field.SetValue(obj, serializedValue.Value);
}
return obj;
}
}
Here is an example of using the surrogate:
using System;
using System.Collections;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
class Foo
{
private static IEnumerator Bar()
{
var now = DateTime.Now;
yield return now.Ticks;
yield return now.Ticks;
}
private static void Main()
{
var e1 = Bar();
// create a surrogate selector
var selector = new SurrogateSelector();
selector.AddSurrogate(
type: e1.GetType(), // the compiler-generated iterator type
context: new StreamingContext(
StreamingContextStates.All), // include all serialization contexts
surrogate: new AnySurrogate() // the surrogate instance
);
using (var mem = new MemoryStream())
{
var binary = new BinaryFormatter
{
SurrogateSelector = selector
};
e1.MoveNext(); // first yield return
Console.WriteLine(e1.Current);
// serialize the iterator instance
binary.Serialize(mem, e1);
mem.Position = 0;
// deserialize the iterator instance
var e2 = (IEnumerator)binary.Deserialize(mem);
e2.MoveNext(); // second yield return
Console.WriteLine(e2.Current);
Console.WriteLine(e2.MoveNext()); // false
}
}
}
A custom implementation of ISurrogateSelector could select a surrogate automatically, removing the need to register each concrete type explicitly. This would extend the approach to other types that are not marked with [Serializable].