常见的Lambda表达式引起的闭包问题
以下代码的原意是想找出list中满足Name字段包含t, Remark字段包含mark的数据
代码
//构造的搜索条件 希望Name包含t remark中包含mark中的文本
Dictionary<string, string> forms = new Dictionary<string, string>();
forms.Add("Name", "t");
forms.Add("Remark", "mark");
//构造一些数据 理论上全部数据都应该满足上面的那个条件 (忽略大小写)
List<UserInformation> list = new List<UserInformation>() {
new UserInformation(){Name ="Test1", Remark="Remark1"},
new UserInformation(){Name ="Test2", Remark="Remark2"},
new UserInformation(){Name ="Test3", Remark="Remark3"},
};
var lambda = list.AsQueryable();
foreach (string key in forms.Keys)
{
string val = forms[key];
if (key == "Name")
lambda = lambda.Where(p => p.Name.Contains(val));//原意是 在条件是Name的时候 对Name字段做过滤
if (key == "Remark")
lambda = lambda.Where(p => p.Remark.Contains(val));//原意是 在条件是Remark的时候 对Remark字段做过滤
}
var data = lambda.ToList();//大家可以注意到结果是0条
var data2 = list.AsQueryable().Where(p => p.Name.Contains(forms["Name"])).Where(p => p.Remark.Contains(forms["Remark"])).ToList();//这个的结果是3条
不过实际情况是data中间一条记录都没有
而hardcode算出来的data2中有3条记录
原因如下:
这个lambda表达式 Where(p=>p.Name.Contains(val)) , 实际上只是保留了一个指向函数外部的val的引用 , 他这个时候并没有把val的真实的值拷贝进来
真正去读取val值的时候是 lambda.ToList() 这个时候才真正执行lambda表达式取数据,过滤数据 ,也是这个时候才去读取val的值
而在foreach的第二次操作的时候 val的值被覆盖mark了 那么就造成了 原来的Where(p=>p.Name.Contains(val)) 变成了 Where(p=>p.Name.Contains("mark"))
注意那个值是mark而不是t
如果我们把三条数据的Name都改成markdafafafadsf 之类的值 那么再次计算data的数据就会变成三条, 大家可以自己弄一下试试,
作用域:
本来val是一个临时变量,他的生命周期应该在foreach结束以后就结束了
但是 由于他被闭包引用,那么val的生命周期延长到引用对象的生命周期(那个lambda不死....val也就会一直活着)