Skip to main content

Serializing Anonymous Methods

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();
    }
}

Usage is like this:

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

Unknown said…
Thanks! I used this article on StackOverflow: http://stackoverflow.com/questions/20947078/serializing-linked-objects

Popular posts from this blog

Enabling Globalization Invariant Mode for .NET Core App on Raspberry Pi Running LibreElec

I had an app I wanted to run on my Raspberry Pi 3 running LibreElec . In LibreElec you can install the dotnet core 2.2 runtime as an addon, and in Visual Studio you can compile for ARM processors with ‘Target Runtime’ set to ‘linux-arm’ in the publish profile. So, I published to a folder from VS using that profile, and I copied the output over to my RPi which had the dotnet runtime installed. I did a simple dotnet Whatever.dll to run the app (actually in this case, it was /storage/.kodi/addons/tools.dotnet-runtime/bin/dotnet Whatever.dll because of the way the addon is installed) and was met with this error: FailFast: Couldn't find a valid ICU package installed on the system. Set the configuration flag System.Globalization.Invariant to true if you want to run with no globalization support. at System.Environment.FailFast(System.String) at System.Globalization.GlobalizationMode.GetGlobalizationInvariantMode() at System.Globalization.GlobalizationMode..cctor() at Syste

Migrating Hg Repos with hg-fast-export and Windows Subsystem for Linux

Introduction I prefer Mercurial (hg) to git . I don’t really have any reason for this preference - they both do the same thing, and the user experience for 90% of the use cases is the same. It probably comes from the conditions of the DVCS landscape when I started using these systems. Some of this may have been perception only, but it looked like this: GitHub didn’t have free private repos BitBucket did have free private repos BitBucket was very hg-friendly Joel Spolsky had an amazing tutorial that served as both a how-to for hg as well as a general intro to DVCS hg was much more Windows-friendly than git Since hg was written in python, I felt like extending it would be easier than doing so for git if I ever needed to (admittedly, this is a pretty ridiculous reason) hg felt like a more unified, “coherent” system than the very linux-y feeling git and its extensions (also pretty ridiculous) Where they differed, I liked the verbs hg used better than git’s counterparts