在C#中序列化匿名委托

时间:2008-11-26 19:14:34

标签: c# .net-3.5 serialization

我正在尝试确定使用以下序列化代理来启用匿名函数/委托/ 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;
    }
}  

清单1 改编自 Counting Demo

我能想到的主要问题可能是一个问题,即匿名类是内部编译器细节,并且不保证它的结构在.NET Framework的修订版之间保持不变。我很确定这是基于我对迭代器的类似问题的研究。

背景

我正在调查匿名函数的序列化。我期待这不起作用,但发现它确实在某些情况下。只要lambda做*不& amp;强制编译器生成一个匿名类一切正常。

如果编译器需要生成的类来实现匿名函数,则抛出SerializationException。这是因为编译器生成的类没有标记为可序列化。

实施例

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.
        }
    }

清单2

这类似于尝试序列化迭代器的问题,我在之前的搜索中找到了以下代码(请参阅countingdemo)使用清单1中的代码< / strong>和一个ISurrogateSelector我能够成功地序列化和反序列化第二个失败的例子。

目的

我有一个通过Web服务公开的系统。系统具有复杂但很小的状态(许多对象,而不是每个对象的很多属性)。状态保存在ASP.NET缓存中,但在缓存过期的情况下也会序列化为SQL中的BLOB。某些对象在达到某些条件时需要执行任意“事件”。因此,它们具有接受Action / Func对象的属性。举例:

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

其他地方

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

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

        Store.Save(state);
    }

6 个答案:

答案 0 :(得分:5)

  

某些对象需要执行任意“事件”才能达到某种条件。

这些事件多么武断?他们可以被计算,分配ID并映射到引用吗?

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

答案 1 :(得分:5)

你有没有看到我作为CountingDemo的后续文章写的帖子:http://dotnet.agilekiwi.com/blog/2007/12/update-on-persistent-iterators.html?不幸的是,微软已经确认他们可能会以一种可能导致问题的方式更改编译器详细信息(一天)。 (例如,当您更新到新编译器时,您将无法对在旧(当前)编译器下保存的内容进行反序列化。)

答案 2 :(得分:3)

序列化代表的整个想法非常危险。现在,表达式可能有意义,但即使这很难表达 - 尽管动态LINQ样本在某种程度上允许一种基于文本的表达形式。<​​/ p>

你想用序列化代表做什么究竟是什么?我真的不认为这是个好主意......

答案 3 :(得分:1)

  

由于此状态是本地的,但在尝试设置映射时会导致问题。

本地国家不会为序列化提出完全相同的问题吗?

假设编译器和框架允许它工作:

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

我想t和o也必须序列化。这些方法没有状态,例如。

稍后,您将反序列化目标。你不是得到t和o的新副本吗?这些副本不会与原始t和o的任何更改不同步吗?

另外:你的手册不能用这种方式调用吗?

Other o = FromSomeWhere();
Thing t = OtherPlace();
target.OnWhatever = new DoFooBar() {Other = o, Thing = t} .Run;
target.Save();

答案 4 :(得分:0)

功能图会阻止我在动作/条件中使用本地状态。解决这个问题的唯一方法是为每个需要额外状态的函数创建一个类。

这就是C#编译器使用匿名函数自动执行的操作。我的问题是这些编译器类的序列化。

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

尝试序列化会失败。由于此状态是本地的,但在尝试设置映射时会导致问题。相反,我必须声明这样的事情:

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

然后像这样使用它:

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

        target.OnWhatever = cmd.Run;

        target.Save();

基本上这意味着手动执行C#编译器自动为我做的事情。

答案 5 :(得分:0)

我不是百分之百,但我相信如果你想将一个委托或一些代码“保存”到可以相当动态的数据库中,你需要做的就是创建一个Expression,然后你可以将表达式编译成Func&lt; ...&gt;。

Expression Tree Basics

Late Bound Invocations with Expression Trees