locked
Anonymous methods serialization

    Question

  • Anonymous methods are really useful to leverage methods such as  AppDomain.DoCallBack. Actually, when the anonymous methods does not rely on the context, the produced delegate is serializable. For example, the following code do work

    class Program
    {
        static void Main(string[] args) {
            Foo(delegate() { Console.WriteLine("42"); }); 
        }

        static void Foo(CrossAppDomainDelegate d) {
            MemoryStream stream = new MemoryStream();
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, d);
        }
    }

     


    But this one does not

    class Program
    {
        static void Main(string[] args) {
            int x = 42;
            Foo(delegate() { Console.WriteLine(x); }); 
        }

        static void Foo(CrossAppDomainDelegate d) {
            MemoryStream stream = new MemoryStream();
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, d);
        }
    }

     

    and produces a serialization exception.

    What would you suggest as a workaround to this situation? I can still come back to the old ways (i.e. creating an helper class dedicated to the sole purpose of crossing the boundary), but this solution defeats the benefits of the anonymous methods.

    Joannès
    Tuesday, November 15, 2005 2:46 PM

Answers

  • There are a couple of known issues with anonymous delegates and Remoting.

    These issues are not bugs, but explict design decisions.

    The issue is what do you do with the type that encloses the context and execution?

    Should it be derived from MBRO and/or marked [Serializable]?

    If so, when?

    We decided that we would leave the enclosing type alone for v2.0.

    Sunday, March 12, 2006 12:36 AM
    Moderator

All replies

  • There are a couple of known issues with anonymous delegates and Remoting.

    These issues are not bugs, but explict design decisions.

    The issue is what do you do with the type that encloses the context and execution?

    Should it be derived from MBRO and/or marked [Serializable]?

    If so, when?

    We decided that we would leave the enclosing type alone for v2.0.

    Sunday, March 12, 2006 12:36 AM
    Moderator
  • Arrrgh. this is incredibly annoying. could you not at least have added the Serializable attribute if the enclosing class had it? this would make it workable for people who did need to serialize the object graph containing anonymous dlegates with automatic classes.

    If you want to get fancy you could work out if the vaiables of the class which are referenced which cause the auto generated class are themselves marked as serializable and do that. Heck you could have added a parameter to the Serializable atribute to indicate that autogenerated nested classes were themselves serializable!

    This way makes it possible! and if someone marks the enclosing class as non serializable you're fine.

    You say it is not a bug it is a design descision - MBRO I get. Justify how *not* marking thge class as serializable is in anyway a good design decision given that, in Binary serialization, there is currently no way to say "ignore the Serializable" attribute and just try anyway.

    Either would suffice. You provide neither and ruin an exceptionally useful use case (that you came so close to fixing after the ridiculous "can't serialize a delegate which references a non public method")

    Wednesday, March 29, 2006 12:20 PM
  • Can you please add support for anonymous delegate serialization in 3.0.

    It is really ruining the effect of such a nice feature not having the ability to stream it on disk or remote the object.

    Friday, October 13, 2006 12:32 PM
  • It appears that Microsoft has not provided support for delegate serialization in 3.0.  So, also desiring to have this work, I researched how to serialize anonymous delegates myself.  Here is an example.  It is specific to the Action delegate type, but the same logic could be generalized into a generic type that could wrapper any delegate and make it serializable even if it represents a closure.

     

    Code Block

    [Serializable]

    public class Modification : ISerializable

    {

    Action<TModel> action;

     

    internal Modification(Action<TModel> action)

    {

    this.action = action;

    }

     

    internal Modification(SerializationInfo info, StreamingContext context)

    {

    // Simply retrieve the action if it is serializable

    if (info.GetBoolean("isSerializable"))

    this.action = (Action<TModel>)info.GetValue("action", typeof(Action<TModel>));

    // Otherwise, recreate the action based on its serialized components

    else

    {

    // Retrieve the serialized method reference

    MethodInfo method = (MethodInfo)info.GetValue("method", typeof(MethodInfo));

     

    // Create an instance of the anonymous delegate class

    object target = ReflectionUtil.CreateObject(method.DeclaringType, BindingFlags.NonPublic | BindingFlags.Instance);

     

    // Initialize the fields of the anonymous instance

    foreach (FieldInfo field in method.DeclaringType.GetFields())

    field.SetValue(target, info.GetValue(field.Name, field.FieldType));

     

    // Recreate the action delegate

    action = (Action<TModel>)Delegate.CreateDelegate(typeof(Action<TModel>), target, method.Name);

    }

    }

     

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)

    {

    // Serialize the action delegate directly if the target is serializable

    if (action.Target == null || action.Target.GetType().GetCustomAttributes(typeof(SerializableAttribute), false).Length > 0)

    {

    info.AddValue("isSerializable", true);

    info.AddValue("action", action);

    }

    // Otherwise, serialize information necessary to recreate the action delegate

    else

    {

    info.AddValue("isSerializable", false);

    info.AddValue("method", action.Method);

    foreach (FieldInfo field in action.Method.DeclaringType.GetFields())

    info.AddValue(field.Name, field.GetValue(action.Target));

    }

    }

     

    internal void Apply(TModel model)

    {

    action(model);

    }

    }

     

    Please note that ReflectionUtil is just a utility class that simplifies using reflection.  With a few more lines of code you can easily create the anonymous delegate class instance using standard reflection code.  Essentially, this class stores a reference to a delegate and implements ISerializable in order to perform custom serialization.  During serialization, it determines if the target of the delegate is serializable.  If it is, it just serializes the delegate.  If not, it assumes the delegate is a method on a generated class due to an anonymous delegate representing a closure.  It then serializes the method reference and the values of the fields, which it then uses to reproduce the delegate during deserialization.  Works like a charm.

     

    HTH,

    Jamie

    Tuesday, December 18, 2007 3:24 PM
  • I'm trying to get something similar to the code above working, but its having none of it...


    we get to the end of this code block:

    // Otherwise, serialize information necessary to recreate the action delegate

    else

    {

    info.AddValue("isSerializable", false);

    info.AddValue("method", action.Method);

    foreach (FieldInfo field in action.Method.DeclaringType.GetFields())

    info.AddValue(field.Name, field.GetValue(action.Target));

    }



    then from what I can work out - the compiler generated inner class is still trying to be serialized and of course throwing an exception as its not marked as serializable...

    Any ideas??
    Friday, June 06, 2008 1:30 PM
  • Please ignore the above post...

    I've sorted it now,

    Cheers,

    J.
    Wednesday, June 11, 2008 10:20 AM