在C#中使用lambda表达式或匿名方法时,我们必须警惕对修改后的闭包陷阱的访问.例如:
foreach (var s in strings)
{
query = query.Where(i => i.Prop == s); // access to modified closure
...
}
Run Code Online (Sandbox Code Playgroud)
由于修改后的闭包,上面的代码将导致Where查询中的所有子句都基于最终值s.
正如这里所解释的那样,这是因为上面循环中s声明的变量foreach在编译器中被翻译成这样:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
Run Code Online (Sandbox Code Playgroud)
而不是像这样:
while (enumerator.MoveNext())
{
string s;
s = enumerator.Current;
...
}
Run Code Online (Sandbox Code Playgroud)
正如这里所指出的,在循环外声明变量没有性能优势,在正常情况下,我能想到这样做的唯一原因是你计划在循环范围之外使用变量:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
var finalString = s;
Run Code Online (Sandbox Code Playgroud)
但是,foreach循环中定义的变量不能在循环外使用: …
编辑:我需要更改几个变量的值,因为它们通过计时器运行几次.我需要通过计时器每次迭代不断更新值.我无法将值设置为final,因为这会阻止我更新值,但是我收到了我在下面的初始问题中描述的错误:
我以前写过以下内容:
我收到错误"不能引用在不同方法中定义的内部类中的非final变量".
这种情况发生在双重调用价格和价格调用priceObject上.你知道我为什么会遇到这个问题.我不明白为什么我需要最后的声明.此外,如果你能看到我想要做的是什么,我该怎么做才能解决这个问题.
public static void main(String args[]) {
int period = 2000;
int delay = 2000;
double lastPrice = 0;
Price priceObject = new Price();
double price = 0;
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
price = priceObject.getNextPrice(lastPrice);
System.out.println();
lastPrice = price;
}
}, delay, period);
}
Run Code Online (Sandbox Code Playgroud) 我只是写了一些快速代码并注意到这个编译器错误
在lambda表达式中使用迭代变量可能会产生意外结果.
相反,在循环中创建一个局部变量并为其分配迭代变量的值.
我知道这意味着什么,我可以很容易地解决它,而不是什么大不了的事.
但我想知道为什么在lambda中使用迭代变量是个坏主意?
我以后可能会遇到什么问题?
我正在尝试使用新任务,但发生了一些我不明白的事情.
首先,代码非常简单.我传入一些图像文件的路径列表,并尝试添加一个任务来处理它们中的每一个:
public Boolean AddPictures(IList<string> paths)
{
Boolean result = (paths.Count > 0);
List<Task> tasks = new List<Task>(paths.Count);
foreach (string path in paths)
{
var task = Task.Factory.StartNew(() =>
{
Boolean taskResult = ProcessPicture(path);
return taskResult;
});
task.ContinueWith(t => result &= t.Result);
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
return result;
}
Run Code Online (Sandbox Code Playgroud)
我发现,如果我让它运行,例如,单元测试中的3个路径列表,则所有三个任务都使用提供列表中的最后一个路径.如果我单步执行(并减慢循环的处理速度),则使用循环中的每个路径.
有人可以解释一下发生了什么,为什么?可能的解决方法?
这在C#5.0中正常工作(按预期方式):
var actions = new List<Action>();
foreach (var i in Enumerable.Range(0, 10))
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var act in actions) act();
Run Code Online (Sandbox Code Playgroud)
打印0到9.但是这个显示10次10次:
var actions = new List<Action>();
for (var i = 0; i < 10; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var act in actions) act();
Run Code Online (Sandbox Code Playgroud)
问题:这是我们在5.0之前的C#版本中遇到的问题; 所以我们不得不使用一个循环局部占位符来进行闭包,它现在已经修复了 - 在C#5.0中 - 在"foreach"循环中.但不是"for"循环!
这背后的原因是什么(也没有解决for循环问题)?
我一直在做一些调查,看看我们如何创建一个贯穿树的多线程应用程序.
为了找到如何以最佳方式实现这一点,我创建了一个运行在我的C:\ disk中的测试应用程序并打开所有目录.
class Program
{
static void Main(string[] args)
{
//var startDirectory = @"C:\The folder\RecursiveFolder";
var startDirectory = @"C:\";
var w = Stopwatch.StartNew();
ThisIsARecursiveFunction(startDirectory);
Console.WriteLine("Elapsed seconds: " + w.Elapsed.TotalSeconds);
Console.ReadKey();
}
public static void ThisIsARecursiveFunction(String currentDirectory)
{
var lastBit = Path.GetFileName(currentDirectory);
var depth = currentDirectory.Count(t => t == '\\');
//Console.WriteLine(depth + ": " + currentDirectory);
try
{
var children = Directory.GetDirectories(currentDirectory);
//Edit this mode to switch what way of parallelization it should use
int mode = 3;
switch (mode) …Run Code Online (Sandbox Code Playgroud) 我现在来自功能编程背景,如果我不理解C#中的闭包,请原谅我.
我有以下代码来动态生成获取匿名事件处理程序的按钮:
for (int i = 0; i < 7; i++)
{
Button newButton = new Button();
newButton.Text = "Click me!";
newButton.Click += delegate(Object sender, EventArgs e)
{
MessageBox.Show("I am button number " + i);
};
this.Controls.Add(newButton);
}
Run Code Online (Sandbox Code Playgroud)
我希望"I am button number " + i用ifor循环的迭代值关闭文本.但是,当我实际运行程序时,每个Button都会说I am button number 7.我错过了什么?我正在使用VS2005.
编辑:所以我想我的下一个问题是,如何捕捉价值?
我正在使用带有C#的.NET 4.0任务并行库(我第一次使用TPL)
我有一个任务A,我想在完成一系列其他任务(B,C,D等)之前完成任务.因此,我想创建任务B,C,D等作为任务A的延续.但是,我想将"状态"对象传递给任务B,将另一个状态对象传递给任务C等.
我可以通过简单地使用带有状态对象的Task构造函数重载将状态对象传递给任务A,例如http://msdn.microsoft.com/en-us/library/dd783035.aspx描述了这个Task构造函数重载:
Task(Action<Object>, Object, CancellationToken)
Run Code Online (Sandbox Code Playgroud)
这工作正常,第二个参数是我的"状态"对象.
我想创建一个延续任务,例如任务B:
Task taskB = taskA.ContinueWith(/* args here*/)
Run Code Online (Sandbox Code Playgroud)
但是,我看不到ContinueWith()重载(请参阅http://msdn.microsoft.com/en-us/library/dd235663.aspx),它允许我将"状态"对象传递给延续任务.如何才能做到这一点?
笔记:
对于某些上下文,我正在做的是在几个循环中创建taskB,taskC等,所以我使用状态对象将循环变量的值传递给taskB,taskC等,以避免问题总是以任务中的循环变量的最终值结束(闭包问题).
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
myClass.StartTasks();
}
}
class MyClass
{
int[] arr;
public void StartTasks()
{
arr = new int[2];
arr[0] = 100;
arr[1] = 101;
for (int i = 0; i < 2; i++)
{
Task.Factory.StartNew(() => WorkerMethod(arr[i])); // IndexOutOfRangeException: i==2!!!
}
}
void WorkerMethod(int i)
{
}
}
}
Run Code Online (Sandbox Code Playgroud)
似乎i ++在循环迭代完成之前再次执行.为什么我会得到IndexOutOfRangeException?
我有一个包含几个关键字的列表.我预先通过他们像这样构建我的linq查询(煮沸以消除代码噪音):
List<string> keys = FillKeys()
foreach (string key in keys){
q = q.Where(c => c.Company.Name.Contains(key));
}
Run Code Online (Sandbox Code Playgroud)
当我现在让我的键包含2个键分别返回结果,但不能一起出现(q中的每个项目都是"xyz"或"123",从不"123"和"xyz"),我仍然得到结果.结果集与它到达的最后一个字符串相同.
我查看了linq查询,看起来它创建了正确的sql,但它用相同的(最后一个itterated)值替换了@ p1 AND @ p2.
我究竟做错了什么?