在C#中序列化匿名委托

37

我正在尝试确定使用以下序列化代理可能引起的问题,以便启用匿名函数/委托/ Lambda 的序列化。

// 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 没有强制编译器生成匿名类,一切都很好。

如果编译器需要生成一个类来实现匿名函数,则会抛出 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中的代码和ISurrogateSelector,我能够成功地序列化和反序列化第二个失败示例。

目标

我有一个通过Web服务公开的系统。该系统具有复杂但小型的状态(许多对象,每个对象的属性不多)。该状态保存在ASP.NET Cache中,但也会序列化到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个回答

5

我看到了这篇帖子。猜想这基本上就是我的答案。必须注意这个编译器的变化,以免丢失序列化数据。 - Joseph Kingry
+1,他们确实改了它,我们受到了影响。我用这个发布了答案:https://dev59.com/oZDea4cB1Zd3GeqPdpZi#40780504 - eglasius

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

我写下了我在使用地图进行操作/条件时遇到的问题。 - Joseph Kingry
投票支持你的答案,因为它至少提供了一种替代方案。 - Joseph Kingry

4
整个将委托序列化的想法非常冒险。现在,一个表达式可能有意义,但即使那也很难表示 - 尽管动态LINQ示例可以以某种文本为基础的表达方式实现一定程度上的允许。
你到底想用序列化的委托做什么?我真的认为这不是一个好主意...

我编辑了我的问题,加入了一个客观的部分。我也有一种普遍的“感觉”,这是冒险的,但并没有太多具体的理由来说明它为什么是冒险的。 - Joseph Kingry

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

我希望"t"和"o"被序列化。当状态被反序列化(从数据库中)时,旧对象将会消失,不再存储在内存中,因此冲突将不再是问题。 - Joseph Kingry

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#编译器自动为我执行的操作。


-1

我不是百分之百确定,但我相信如果你想要将一个委托或一些代码“保存”到数据库中并且它们是相当动态的,你需要做的是创建一个表达式,然后你可以将表达式编译成一个 Func<...>。

表达式树基础

使用表达式树进行晚期绑定调用


Other o = new Other(); Expression<Func<int>> fails = () => o.Value; f.Serialize(m, fails); // 再次抛出SerializationException! - 9b5b

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接