【问题标题】:Is there a way to store an anonymous delegate in a viewstate?有没有办法将匿名委托存储在视图状态中?
【发布时间】:2012-08-28 11:36:57
【问题描述】:

WebControl 中,我有一个属性Filters 定义如下:

public Dictionary<string, Func<T, bool>> Filters
{
    get
    {
       Dictionary<string, Func<T, bool>> filters =
               (Dictionary<string, Func<T, bool>>)ViewState["filters"];
       if (filters == null)
       {
          filters = new Dictionary<string, Func<T, bool>>();
          ViewState["filters"] = filters;
       }
       return filters;
    }
 }

这个webcontrol是DataSource,我创建这个属性是因为我想有可能轻松过滤数据,例如:

//in page load    
DataSource.Filters.Add("userid", u => u.UserID == 8);

但是,如果我将代码更改为以下代码,效果会很好:

//in page load    
int userId = int.Parse(DdlUsers.SelectedValue);
DataSource.Filters.Add("userid", u => u.UserID == userId);

它不再工作了,我得到这个错误:

Assembly '...' 中的类型 System.Web.UI.Page 未标记为 可序列化。

发生了什么:

  1. 序列化程序检查字典。它看到它包含一个匿名委托(这里是 lambda)
  2. 由于委托是在一个类中定义的,它会尝试序列化整个类,在本例中为 System.Web.UI.Page
  3. 该类未标记为可序列化
  4. 因为 3 抛出异常。

有什么方便的解决方案可以解决这个问题吗?由于显而易见的原因,我无法将使用数据源的所有网页标记为 [可序列化]。


EDIT 1:我不明白的东西。如果我将Dictionary 存储在Session 对象中(使用BinaryFormatterLosFormatter 代表ViewState),它就可以工作!我不知道怎么可能。也许BinaryFormatter 可以序列化任何类,即使是那些不是[serializable] 的类?


EDIT 2:重现问题的最小代码:

void test()
{
    Test test = new Test();
    string param1 = "parametertopass";
    test.MyEvent += () => Console.WriteLine(param1);

    using (MemoryStream ms = new MemoryStream())
    {
       BinaryFormatter bf = new BinaryFormatter();
       bf.Serialize(ms, test); //bang
    }
}

[Serializable]
public class Test
{
   public event Action MyEvent;
}

【问题讨论】:

  • “它有效!我不知道如何......”:会话数据保留在服务器端,在内存中。当您移动到 ​​2 个以上的服务器时,它将开始中断。

标签: c# serialization delegates webforms viewstate


【解决方案1】:

好问题。我可以确认您的诊断(稍作修正:基础结构尝试序列化可能包含对您的页面的引用的闭包类)。

您可以定义自己的闭包类并将其序列化:

[Serializable] class Closure { int userId; bool Filter(User u) { ... } };

不过这样不太方便。

我建议您使用不同的模式:不要序列化“代码”。序列化过滤器使用的数据:

class FilterSettings { int userId; int someOtherFiler; string sortOrder; ... }

虽然我无法指出我更愿意凭直觉知道这是一种更好的方法的确切原因。

【讨论】:

  • 感谢您的回答。我不确定第二个提议将如何运作。谁将实例化并持有 FilterSettings 类的实例?如果它是页面,序列化程序是否也有到达页面的风险?你能提供更多关于如何使用这个解决方案的代码吗?
  • 存储您精确控制的字段永远不会引用页面的类。序列化程序无法访问从 Closure 或 FilterSettings 开始的页面。
  • 即使有你的建议,我仍然无法让它工作。我在问题的底部添加了一个代码示例,可以重现该问题。你能看看并给我提示吗?
  • 我猜编译器创建的闭包类不是[Serializable]。这就是为什么你需要自己动手。如果你想序列化一个函数,使该函数成为闭包类的实例成员,并像这样创建委托:new Action(myClosure.InstanceMethod)。你不能使用 lambda。
【解决方案2】:

我找到了解决方案,我是这样做的:

我这样修改字典定义:

Dictionary<string, KeyValuePair<Func<T, object[], bool>, object[]>>

KeyValuePair 是可序列化的,就像Dictionary

并创建了一个新的Add() 函数:

public void Add(string key, Func<T, object[], bool> filter, params object[] args)
{
    this.Add(key, new KeyValuePair<Func<T, object[], bool>, object[]>
          (filter, args));
}

现在过滤器可以这样设置:

int userId = int.Parse(DdlUsers.SelectedValue);
DataSource.Filters.Add("userid", (u, args) => u.UserID == (int)args[0], userId);

它起作用了,因为现在捕获的变量不再是委托的一部分,而是作为委托的参数给出。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-11-25
    • 2016-04-17
    • 2014-03-02
    • 1970-01-01
    • 2010-11-01
    • 1970-01-01
    • 2013-09-01
    相关资源
    最近更新 更多