2011-03-17 44 views
1

我有访问修改关闭了字符串变量的警告。访问修改的关闭警告是否对字符串变量有效?

foreach (string s in splits) 
{ 
    regexes.Where(x => x.Pattern.Contains(s)); 
} 

离开代码的方式是否安全?我认为使用lambda创建的委托将按值接收字符串,因为字符串是不可变的,每个新的“s”都会偏向不同的内存。

感谢

+0

[Access to Modified Closure]的可能重复(http://stackoverflow.com/questions/235455/access-to-modified-closure) – Jon 2011-03-17 09:56:44

回答

2

我认为使用lambda创建的委托会按值接收字符串,因为字符串是不可变的,每个新的“s”都会偏向不同的内存。

创建委托并不是评估的变量。这是对关闭整点:他们收,较变量。所以,你所有的lambda会捕获相同的变量s,这可能不是你想要的。如果它影响你的代码,则取决于评估lambda表达式的时间:如果Where的调用已经“执行”了lambda表达式(然后将它抛出),那么你应该没问题。如果您的regexes对象存储了lambdas并稍后使用它们,那么您遇到了麻烦。

因此,为了安全起见,首先将该值复制到循环内部的局部变量中。

+0

谢谢。如果我正确地做到这一点,这意味着委托接收指向变量的指针,而不是它自身的变量值。 – Marek 2011-03-17 10:06:03

+2

@marek:简而言之,是的。编译器实际上创建了一个辅助类,将你的变量和委托移动到辅助类中,并与你的代码共享该类的一个实例。这样,你的代码和委托都有一个指向同一个变量的“指针”。这在这里详细解释:http://blogs.msdn.com/b/oldnewthing/archive/2006/08/02/686456.aspx – Heinzi 2011-03-17 10:09:26

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)); 
} 

重新任何“更好的方式”(评论)......嗯,克隆的可变招有工作的优势,但这里有一个可爱的绝招 - 修复了上述“恶意代码“:

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)); 
} 
+0

谢谢,这实际上是我想要做的。有没有更好的方法来建立where子句? – Marek 2011-03-17 10:10:28

+0

@marek - 我会更新... – 2011-03-17 10:12:21

1

看起来并不安全,我通常尽可能避免警告,因为它们经常表明潜在的问题。

谷歌搜索"access to modified closure"带来了一些看似相关的命中,包括#2,Linq: Beware of the 'Access to modified closure' demon,你一定要在仔细观察。

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的延迟执行的执行咬伤,我真的不认为这是一个问题,以这种方式使用lambda表达式,因为大多是拉姆达的整个生命周期是在foreach的范围之内。

+0

什么C#版本你尝试过吗? VS2010/C#4打印5,6,34。经过一番玩弄,似乎他们已经改变了foreach循环的行为,现在它“按预期”工作。请注意,虽然for循环仍然存在问题。 – stmax 2012-03-22 19:50:47

+0

我刚刚试过用VS2010/C#4,我仍然得到34,34,34。你确定你没有安装C#5测试版,因为这将在C#5中修复。 – SWeko 2012-03-23 16:02:55