¿Hay alguna manera de almacenar un delegado anónimo en un estado de vista?
Frecuentes
Visto 1,040 veces
6
En una WebControl
, i have a property Filters
definido así:
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;
}
}
This webcontrol is a DataSource
, i created this property because i want to have the possiblity to filter data easily, eg:
//in page load
DataSource.Filters.Add("userid", u => u.UserID == 8);
It works great, however, if I change code to this :
//in page load
int userId = int.Parse(DdlUsers.SelectedValue);
DataSource.Filters.Add("userid", u => u.UserID == userId);
It doesn't works anymore, I get this error :
Type System.Web.UI.Page in Assembly '...' is not marked as serializable.
What happened :
- The serializer inspect the dictionary. It sees it contains a anonymous delegate (lambda here)
- Since the delegate is defined in a class, it tries to serialize the whole class, in this case System.Web.UI.Page
- This class is not marked as Serializable
- It throws an exception because of 3.
Is there any convenient solution to solve this ? I cannot mark all web pages where i use the datasource as [serializable] for obvious reasons.
EDIT 1 : something I don't understand. If I store the Dictionary
en el capítulo respecto a la Session
object (which use a BinaryFormatter
vs LosFormatter
por ViewState
), it works ! I have no idea how it is possible. Maybe BinaryFormatter
can serialize any class, even these who are not [serializable]
?
EDIT 2 : smallest code to reproduce the problem :
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 Respuestas
4
Good question. I can confirm your diagnose (with one tiny correction: The infrastructure tries to serialize the closure class which probably contains a reference to your page).
You can define your own closure class and have that serialized:
[Serializable] class Closure { int userId; bool Filter(User u) { ... } };
That is not convenient, though.
I suggest you use a different pattern: Don't serialize "code". Serialize the data used by the filter:
class FilterSettings { int userId; int someOtherFiler; string sortOrder; ... }
Although I cannot point to the exact reason why I would prefer that I intuitively know that is a better approach.
Respondido 28 ago 12, 12:08
Thanks for you answer. I am not sure how second proposition will work. Who will instantiate and hold the instance of FilterSettings class ? If its the page, isn't there any risk that serializer will reach the page as well ? Can you provide more code about how to use this solution ? - tigrou
Storing a class whose fields you precisely control will never reference the page. There is no way the serializer can reach the page starting from Closure or FilterSettings. - usr
I still cannot make it work even with you suggestions. I have added a sample of code at the bottom of question that will reproduce the problem. Can you have a look and give me hint ? - tigrou
I guess the compiler-created closure class is not [Serializable]. That's why you need to roll your own. If you want to serialize a function, make that function an instance member of the closure class and create the delegate like this: new Action(myClosure.InstanceMethod). You can't use a lambda. - usr
0
I found a solution, here is how i did it :
I modified Dictionary definition like this:
Dictionary<string, KeyValuePair<Func<T, object[], bool>, object[]>>
(KeyValuePair
is serializable, just like Dictionary
)
y creó un nuevo Add()
función:
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));
}
Now filters can be set this way :
int userId = int.Parse(DdlUsers.SelectedValue);
DataSource.Filters.Add("userid", (u, args) => u.UserID == (int)args[0], userId);
It works, because now captured variables are no more a part the delegate, but given as parameters of the delegate.
Respondido el 21 de diciembre de 12 a las 11:12
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas c# serialization delegates webforms viewstate or haz tu propia pregunta.
"it works ! I have no idea how ..." : Session data remains server-side, in-memory. It will start to break when you move to 2+ servers. - Henk Holterman