views:

3276

answers:

6

I am trying to determine what issues could be caused by using the following serialization surrogate to enable serialization of anonymous functions/delegate/lambdas.

// see http://msdn.microsoft.com/msdnmag/issues/02/09/net/#S3
class NonSerializableSurrogate : ISerializationSurrogate
{
    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        foreach (FieldInfo f in obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
            info.AddValue(f.Name, f.GetValue(obj));
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context,
                                ISurrogateSelector selector)
    {
        foreach (FieldInfo f in obj.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
            f.SetValue(obj, info.GetValue(f.Name, f.FieldType));
        return obj;
    }
}

Listing 1 adapted from Counting Demo

The main issue I can think of that might be a problem is that the anonymous class is an internal compiler detail and it's structure is not guaranteed to remain constant between revisions to the .NET Framework. I'm fairly certain this is the case based on my research into the similar problem with iterators.

Background

I am investigating the serialization of anonymous functions. I was expecting this not to work, but found it did for some cases. As long as the lambda did *not& force the compiler to generate an anonymous class everything works fine.

A SerializationException is thrown if the compiler requires a generated class to implement the anonymous function. This is because the compiler generated class is not marked as serializable.

Example

namespace Example
{
    [Serializable]
    class Other
    {
        public int Value;
    }

    [Serializable]
    class Program
    {
        static void Main(string[] args)
        {
            MemoryStream m = new MemoryStream();
            BinaryFormatter f = new BinaryFormatter();

            // Example 1
            Func<int> succeeds = () => 5;
            f.Serialize(m, succeeds);

            // Example 2
            Other o = new Other();
            Func<int> fails = () => o.Value;
            f.Serialize(m, fails); // throws SerializationException - Type 'Example.Program+<>c__DisplayClass3' in Assembly 'Example, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.
        }
    }

Listing 2

This is similar to the issue of trying to serialize iterators and I had found the following code in a previous search (see [countingdemo]) Using the code from Listing 1 and an ISurrogateSelector I was able to successfully serialize and deserialize the second failing example.

Objective

I have a system that is exposed via a web service. The system has a complex but small state (many objects, not a lot of properties per object). The state is saved in the ASP.NET Cache, but is also serialized to a BLOB in SQL in case of cache expiration. Some objects need to execute arbitrary "events" upon reaching some condition. Hence they have properties accepting Action/Func objects. Contrived example:

    class Command
    {
        public Command(Action action, Func<bool> condition);
    }

Somewhere else

    void DoSomethingWithThing(Thing thing)
    {
        state = Store.GetCurrentState();

        Command cmd = new Command(() => thing.Foo(), () => thing.IsReady())
        state.Add(cmd);

        Store.Save(state);
    }
+1  A: 

The whole idea of serializing a delegate is very risky. Now, an expression might make sense, but even that is hard to express - although the dynamic-LINQ samples go some way to allowing a form of text-based expression.

What exactly is it you want to do with a serialized delegate? I really don't think this is a good idea...

Marc Gravell
I edited my question adding an objective section. I too have the general "feeling" this is risky, but not a lot of concrete reasons as to why it's risky.
Joseph Kingry
+2  A: 

Some objects need execute arbitrary "events" reaching some condition.

Just how arbitrary are these events? Can they be counted, assigned ID's and mapped to referentially?

public class Command<T> where T : ISerializable
{
  T _target;
  int _actionId;
  int _conditionId;

  public Command<T>(T Target, int ActionId, int ConditionId)
  {
    _target = Target;
    _actionId = ActionId;
    _conditionId = ConditionId;
  }

  public bool FireRule()
  {
    Func<T, bool> theCondition = conditionMap.LookupCondition<T>(_conditionId)
    Action<T> theAction = actionMap.LookupAction<T>(_actionId);

    if (theCondition(_target))
    {
      theAction(_target);
      return true;
    }
    return false;
  }  
}
David B
I wrote out the issue I have with using a map for actions/conditions.
Joseph Kingry
Voting for your answer as at least it presented an alternative.
Joseph Kingry
A: 

A function map would prevent me from using local state in the action/conditions. The only way around this would work would be to create a class per function that required additional state.

This is what the C# compiler is doing automatically for me with anonymous functions. My issue is the serialization of these compiler classes.

        Other o = FromSomeWhere();
        Thing t = OtherPlace();
        target.OnWhatever = () => t.DoFoo() + o.DoBar();
        target.Save();c

Trying to serialize that would fail. Since this state is local though that leads to issues when trying to setup a mapping. Instead I'd have to declare something like this:

[Serializable]
abstract class Command<T>
{
    public abstract T Run();
}

class DoFooBar : Command<int>
{
    public Other Other { get; set; }
    public Thing Thing { get; set; }

    public override int Run()
    {
        return Thing.DoFoo() + Other.DoBar(); 
    }
}

and then use it like this:

        DoFooBar cmd = new DoFooBar();
        cmd.Other = FromSomewhere();
        cmd.Thing = OtherPlace();

        target.OnWhatever = cmd.Run;

        target.Save();

Essentially what this means is doing manually what the C# compiler is doing for me automatically.

Joseph Kingry
A: 

Since this state is local though that leads to issues when trying to setup a mapping.

Wouldn't local state present the exact same problems for serialization?

Suppose the compiler and the framework allowed this to work:

Other o = FromSomeWhere();
Thing t = OtherPlace();
target.OnWhatever = () => t.DoFoo() + o.DoBar();
target.Save();

I guess t and o had to be serialized too. The methods don't have the state, the instances do.

Later, you deserialize target. Don't you get new copies of t and o? Won't these copies be out of sync with any changes to the original t and o?

Also: couldn't your manual example be called this way?

Other o = FromSomeWhere();
Thing t = OtherPlace();
target.OnWhatever = new DoFooBar() {Other = o, Thing = t} .Run;
target.Save();
David B
I want "t" and "o" to be serialized. When the state is deserialized (from the database) the old objects will be gone, no longer in memory, so conflicts aren't an issue.
Joseph Kingry
+1  A: 

Did you see this post that I wrote as a followup to the CountingDemo: http://dotnet.agilekiwi.com/blog/2007/12/update-on-persistent-iterators.html ? Unfortunately, Microsoft have confirmed that they probably will change the compiler details (one day), in a way that is likely to cause problems. (e.g. f/when you update to the new compiler, you won't be able to deserialise the stuff you saved under the old (current) compiler.)

I saw this post. Guess this is essentially my answer. Will have to watch out for this compiler change so I don't loose serialized data.
Joseph Kingry
A: 

I'm not 100% on this, but I believe that if you want to "save" a delegate or some code to the database that can be fairly dynamic, what you need to do is create an Expression, then you can compile the expression into a Func<...>.

Expression Tree Basics

Late Bound Invocations with Expression Trees

viggity
Other o = new Other(); Expression<Func<int>> fails = () => o.Value; f.Serialize(m, fails); // throws SerializationException again!
Jon Schoning