有没有一种方法可以将匿名委托存储在视图状态中?

6
在一个 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 是一个数据源,我创建了这个属性是因为我想要轻松地过滤数据,例如:
//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);

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

程序集“...”中的类型System.Web.UI.Page未标记为可序列化。

发生了什么:

  1. 序列化器检查字典。它看到其中包含一个匿名委托(lambda)
  2. 由于委托是在类中定义的,因此它尝试序列化整个类,在这种情况下是System.Web.UI.Page
  3. 该类未标记为可序列化
  4. 由于3的原因,它引发异常。

是否有任何方便的解决方法来解决这个问题?由于明显的原因,我无法将我使用数据源的所有网页都标记为[serializable]。


编辑1:我不理解的一些事情。如果我将Dictionary存储在Session对象中(使用BinaryFormatter而不是LosFormatter进行ViewState),它就可以工作!我不知道这是如何可能的。也许BinaryFormatter可以序列化任何类,即使它们不是[serializable]


编辑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个或更多服务器时,它将开始出现问题。" - H H
2个回答

4

很好的问题。我可以确认你的诊断(稍作更正:基础设施试图序列化闭包类,该类可能包含对你的页面的引用)。

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

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

虽然那种方式不太方便。

我建议您使用不同的模式:不对“代码”进行序列化。而是对筛选器所使用的数据进行序列化:

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

虽然我无法指出确切的原因,但我直觉地知道这是更好的方法。

在IT技术方面。

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

0
我找到了一个解决方案,这是我的做法:
我修改了字典的定义,像这样:
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);

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


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