在C#中序列化匿名代表

我想确定使用下面的序列化代理来启用匿名函数/委托/ 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不*强制编译器生成一个匿名类一切正常。

如果编译器需要生成的类来实现匿名函数,则会引发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

这与尝试序列化迭代器的问题类似,我在之前的search中find了以下代码(请参阅countingdemo )使用清单1中的代码和一个ISurrogateSelector,我能够成功序列化和反序列化第二个失败的示例。

目的

我有一个通过Web服务暴露的系统。 系统有一个复杂但很小的状态(许多对象,每个对象的属性不是很多)。 状态保存在ASP.NETcaching中,但在caching过期的情况下,也会将其序列化为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); } 

你有没有看到这个post,我写了一个CountingDemo的后续: http : //dotnet.agilekiwi.com/blog/2007/12/update-on-persistent-iterators.html ? 不幸的是,微软已经证实,他们可能会改变编译器的细节(有一天),以一种可能导致问题的方式。 (比如f /当你更新到新的编译器时,你将不能反序列化在旧的(当前)编译器下保存的东西。)

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

这些事件是多么的随意? 他们可以被计数,分配的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; } } 

序列化委托的整个想法是非常危险的。 现在, expression式可能是有意义的,但即使这样也很难expression,尽pipedynamicLINQ示例采用某种方式来允许一种基于文本的expression式。

你想用序列化委托什么? 我真的不认为这是一个好主意…

由于这个状态是本地的,但是在尝试build立映射时会导致问题。

本地状态不会出现序列化完全相同的问题吗?

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

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

我不是100%,但我相信如果你想“保存”一个代表或者一些代码到数据库中,这个代码可以是相当dynamic的,你需要做的是创build一个expression式,然后你可以编译expression式到一个Func <…>。

expression式树基础

expression式树的后期调用

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

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

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

尝试序列化会失败。 由于这个状态是本地的,但是在尝试build立映射时会导致问题。 相反,我不得不声明这样的东西:

 [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#编译器自动为我做的事情。