我有一个警告,可以访问字符串变量的修改后的闭包。
foreach (string s in splits)
{
regexes.Where(x => x.Pattern.Contains(s));
}
保持代码的安全性是否安全?我假设使用lambda创建的委托将按值接收字符串,因为字符串是不可变的,并且每个新的“s”将对不同的内存进行操作。
由于
答案 0 :(得分:3)
在这种情况下,它不会对您造成伤害,因为您没有在外部循环的地方使用委托。当然,你并没有真正在示例中使用任何,所以目前还不清楚这是否“代表”实际代码。
线程和延迟回调会受到影响,所以是的(在某种程度上)是有效的。如果不确定,请修复它:
foreach (string s in splits)
{
var clone = s;
regexes.Where(x => x.Pattern.Contains(clone));
}
为了澄清,当捕获的变量在foreach循环的正常流程之外使用时,此问题仅伤害。当直接在循环中使用并在同一个线程(无Parallel
)上时,捕获的变量将始终处于预期值。任何破坏的东西都会看到随机数据;通常(但不总是)最后值。
请注意 这个“延期委托”包括在查询中构建Where
过滤器等内容,因为您会发现它们都使用了最后一个值。以下情况会很糟糕:
var query = ...
foreach (string s in splits)
{ // BAD CODE!
query = query.Where(x => x.Foo.Contains(s));
}
任何“更好的方式”(评论)......好吧,克隆的变量技巧具有工作的优势,但这里有一个可爱的伎俩 - 修复上面的“BAD CODE!”:
query = splits.Aggregate(query, (tmp, s) => tmp.Where(x => x.Foo.Contains(s)));
我个人认为以下内容更容易理解:
foreach (string s in splits)
{
var tmp = s;
query = query.Where(x => x.Foo.Contains(tmp));
}
答案 1 :(得分:2)
我假设使用lambda创建的委托将按值接收字符串,因为字符串是不可变的,并且每个新的“s”将对不同的内存进行操作。
创建委托不会评估变量。这是关于闭包的全部观点:它们关闭变量。所以,你所有的lambda都将捕获相同的变量s
,这可能不是你想要的。如果它影响你的代码取决于lambda被评估的时间:如果Where
的调用已经“执行”了lambda(然后扔掉它),你应该没问题。如果你的regexes
对象存储lambdas并在以后使用它们,那你就麻烦了。
因此,为了安全起见,首先将值复制到循环内部的局部变量。
答案 2 :(得分:1)
看起来不安全,我一般尽量避免发出警告,因为它们经常表明存在潜在问题。
谷歌搜索"access to modified closure"
会带来一系列看似相关的点击,包括#2 Linq: Beware of the 'Access to modified closure' demon您肯定希望仔细查看。
答案 3 :(得分:1)
这是一个简单的例子,证明这对字符串来说是不安全的,不可变的。
List<Func<int>> lambdas = new List<Func<int>>();
List<string> strings = new List<string>
{ "first", "second", "supercalifragilisticexpialidocious" };
foreach (string s in strings)
{
lambdas.Add(new Func<int>(() => s.Length));
}
Console.WriteLine(lambdas[0]()); //34
Console.WriteLine(lambdas[1]()); //34
Console.WriteLine(lambdas[2]()); //34
然而,除了被LINQ的延迟执行所困扰之外,我并不认为以这种方式使用lambdas是一个问题,因为lambda的整个生命周期大部分都在foreach的范围内。