循环优化或lambda闭包的问题?

在下面的方法中,我发送一个动作的枚举,并希望返回一个ICommands数组,调用Action来包装那些动作(relayCommand所需)。

问题是,如果我在每个内部(甚至是for循环)中执行此操作,我将获得始终执行参数中传递的第一个操作的命令。

  public static ICommand[] CreateCommands(IEnumerable actions) { List commands = new List(); Action[] actionArray = actions.ToArray(); // works //commands.Add(new RelayCommand(o => { actionArray[0](); })); // (_execute = {Method = {Void b__0(System.Object)}}) //commands.Add(new RelayCommand(o => { actionArray[1](); })); // (_execute = {Method = {Void b__1(System.Object)}}) foreach (var action in actionArray) { // always add the same _execute member for each RelayCommand (_execute = {Method = {Void b__0(System.Object)}}) commands.Add(new RelayCommand(o => { action(); })); } return commands.ToArray(); } 

似乎lambda总是在循环中重用,认为它做的相同,但事实并非如此。

我该如何克服这种情况? 我如何强制循环威胁o => { action(); } 总是作为一个新的?

谢谢!

我根据建议尝试了什么,但没有帮助:

  foreach (var action in actionArray) { Action executeHandler = o => { action(); }; commands.Add(new RelayCommand(executeHandler)); } 

似乎对我有用的是:

  class RelayExecuteWrapper { Action _action; public RelayExecuteWrapper(Action action) { _action = action; } public void Execute(object o) { _action(); } } /// ... foreach (var action in actionArray) { RelayExecuteWrapper rxw = new RelayExecuteWrapper(action); commands.Add(new RelayCommand(rxw.Execute)); } 

继电器代码:

 ///  /// A command whose sole purpose is to /// relay its functionality to other /// objects by invoking delegates. The /// default return value for the CanExecute /// method is 'true'. ///  public class RelayCommand : ICommand { #region Fields readonly Action _execute; readonly Predicate _canExecute; #endregion // Fields #region Constructors ///  /// Creates a new command that can always execute. ///  /// The execution logic. public RelayCommand(Action execute) : this(execute, null) { } ///  /// Creates a new command. ///  /// The execution logic. /// The execution status logic. public RelayCommand(Action execute, Predicate canExecute) { if (execute == null) throw new ArgumentNullException("execute"); _execute = execute; _canExecute = canExecute; } #endregion // Constructors #region ICommand Members [DebuggerStepThrough] public bool CanExecute(object parameter) { return _canExecute == null ? true : _canExecute(parameter); } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public void Execute(object parameter) { _execute(parameter); } #endregion // ICommand Members } 

在StackOverflow上每周报告几次此问题。 问题是在循环内创建的每个新lambda共享相同的 “action”变量。 lambda不捕获值,它们捕获变量。 也就是说,当你说

 List list = new List(); foreach(int x in Range(0, 10)) list.Add( ()=>{Console.WriteLine(x);} ); list[0](); 

那当然打印“10”,因为这是x的值。 操作是“写入x的当前值”,而不是“写入创建委托时x返回的值”。

要解决此问题,请创建一个新变量:

 List list = new List(); foreach(int x in Range(0, 10)) { int y = x; list.Add( ()=>{Console.WriteLine(y);} ); } list[0](); 

由于这个问题很常见,我们正在考虑更改下一版本的C#,以便每次通过foreach循环创建一个新变量。

有关详细信息,请参阅http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/ 。

更新:来自评论:

每个ICommand都有相同的methodinfo:

 { Method = {Void b__0(System.Object)}} 

是的,当然可以。 每次方法都是一样的。 我认为你误解了代表创作是什么。 以这种方式看待它。 假设你说:

 var firstList = new List>() { ()=>10, ()=>20 }; 

好的,我们有一个返回int的函数列表。 第一个返回10,第二个返回20。

这与以下内容相同:

 static int ReturnTen() { return 10; } static int ReturnTwenty() { return 20; } ... var firstList = new List>() { ReturnTen, ReturnTwenty }; 

到目前为止有道理吗? 现在我们添加你的foreach循环:

 var secondList = new List>(); foreach(var func in firstList) secondList.Add(()=>func()); 

好的, 是什么意思? 这意味着完全相同的事情:

 class Closure { public Func func; public int DoTheThing() { return this.func(); } } ... var secondList = new List>(); Closure closure = new Closure(); foreach(var func in firstList) { closure.func = func; secondList.Add(closure.DoTheThing); } 

现在很清楚这里发生了什么? 每次循环都不会创建一个新的闭包,你肯定不会创建一个新的方法。 您创建的委托始终指向相同的方法,并始终指向相同的闭包。

现在,如果相反你写了

 foreach(var loopFunc in firstList) { var func = loopFunc; secondList.Add(func); } 

然后我们将生成的代码

 foreach(var loopFunc in firstList) { var closure = new Closure(); closure.func = loopFunc; secondList.Add(closure.DoTheThing); } 

现在列表中的每个新函数都有相同的methodinfo – 它仍然是DoTheThing – 但是一个不同的闭包

现在,为什么你看到你的结果是有道理的?

您可能还想阅读:

在C#中由lambda创建的委托的生命周期是多少?

另一个更新:从编辑过的问题:

我根据建议尝试了什么,但没有帮助:

  foreach (var action in actionArray) { Action executeHandler = o => { action(); }; commands.Add(new RelayCommand(executeHandler)); } } 

当然这没有帮助。 这和以前完全一样。 问题是lambda在单个变量’action’上关闭,而不是在每个action值上关闭。 在lambda创建的地方移动显然不能解决这个问题。 你想要做的是创建一个新变量 。 您的第二个解决方案是通过创建引用类型字段来分配新变量。 你不需要明确地这样做; 正如我上面提到的,如果你在循环体内部创建一个新变量,编译器将为你这样做。

解决问题的正确和简短方法是

  foreach (var action in actionArray) { Action copy = action; commands.Add(new RelayCommand(x=>{copy();})); } 

这样,每次循环都会创建一个新变量 ,因此循环中的每个lambda都会关闭另一个变量

每个委托将具有相同的methodinfo但具有不同的闭包

我不太确定这些封闭和lambdas

你正在程序中进行高阶函数式编程。 如果你想有机会这样做,你最好学习“这些闭包和lambdas”。 没有时间像现在这样。

我刚刚做了一个确实正在尝试做的工作的例子: http : //ideone.com/hNcGx

  interface ICommand { void Print(); } class CommandA : ICommand { public void Print() { Console.WriteLine("A"); } } class CommandB : ICommand { public void Print() { Console.WriteLine("B"); } } public static void Main() { var actions = new List(); foreach (var command in new ICommand[]{ new CommandA(), new CommandB(), new CommandB()}) { var commandcopy = command; actions.Add(() => commandcopy.Print()); } foreach (var action in actions) action(); } 

输出:

 A B B 

这对你有帮助吗?

在循环范围内对action进行本地引用。

 foreach (var action in actionArray) { var myAction = action; // always add the same _execute member for each RelayCommand (_execute = {Method = {Void b__0(System.Object)}}) commands.Add(new RelayCommand(o => { action(); })); } 

您只使用actionArray数组中的第一项。

 commands.Add(new RelayCommand(o => { actionArray[0](); })); 

您需要遍历操作集合。

例如

 public static ICommand[] CreateCommands(IEnumerable actions) { commands = actions.Select(o => new RelayCommand(o)).ToArray(); } 

代码是徒手的,所以可能是一些错别字,但应该指出正确的想法。