问题描述
在下面的方法我送的动作的枚举和想要个ICommand阵列回了电话动作<对象>
的包装这些行动(所需relayCommand )。
问题是,如果我做这里面的每一个(甚至是一个for循环),我得到的总是执行中的参数传递的第一个动作命令。
公共静态的ICommand [] CreateCommands(IEnumerable的<作用>的行动)
{
名单< ICommand的>命令=新的名单,其中,ICommand的>();
操作[] actionArray = actions.ToArray();
// 作品
//commands.Add(new RelayCommand(O => {actionArray [0]();})); //(_execute = {方法= {无效< CreateCommands> b__0(System.Object的)}})
//commands.Add(new RelayCommand(O => {actionArray [1]();})); //(_execute = {方法= {无效< CreateCommands> b__1(System.Object的)}})
的foreach(在actionArray VAR行动)
{
//随时添加相同_execute成员每个RelayCommand(_execute = {方法= {无效< CreateCommands> b__0(System.Object的)}})
commands.Add(新RelayCommand(O => {动作();}));
}
返回commands.ToArray();
}
看来,拉姆达总是重复使用循环中认为它确实是相同的,但事实并非如此。
我如何克服这种情况?我怎么能强制循环威胁 O => { 行动(); }
总是作为一个新的?
谢谢!
我试着按照建议,但并没有帮助:
的foreach(在actionArray VAR行动)
{
动作<对象> executeHandler = O => { 行动(); };
commands.Add(新RelayCommand(executeHandler));
}
似乎什么工作对我来说是:
类RelayExecuteWrapper
{
行动_action;
公共RelayExecuteWrapper(动作的动作)
{
_action =行动;
}
公共无效执行(对象o)
{
_行动();
}
}
/// ...
的foreach(在actionArray VAR行动)
{
RelayExecuteWrapper箭在弦上=新RelayExecuteWrapper(动作);
commands.Add(新RelayCommand(rxw.Execute));
}
RelayCommand的code:
///<总结>
///一个命令,其唯一目的是
///继电器其功能与其他
///通过调用委托对象。该
对于CanExecute ///默认返回值
///方法是真。
///< /总结>
公共类RelayCommand:ICommand的
{
#地区的字段
只读动作<对象> _执行;
只读predicate<对象> _canExecute;
#endregion //领域
#区域构造
///<总结>
///创建一个新的命令可以随时执行。
///< /总结>
///< PARAM NAME =执行>在执行逻辑LT; /参数>
公共RelayCommand(动作<对象>执行)
:这个(执行,空)
{
}
///<总结>
///创建一个新的命令。
///< /总结>
///< PARAM NAME =执行>在执行逻辑LT; /参数>
///< PARAM NAME =canExecute>执行状态逻辑LT; /参数>
公共RelayCommand(动作<对象>执行,predicate<对象> canExecute)
{
如果(执行== NULL)
抛出新ArgumentNullException(执行);
_execute =执行;
_canExecute = canExecute;
}
#endregion //构造函数
#地区的ICommand成员
[DebuggerStepThrough]
公共BOOL CanExecute(对象参数)
{
返回_canExecute == NULL?真:_canExecute(参数);
}
公共事件的EventHandler CanExecuteChanged
{
添加{CommandManager.RequerySuggested + =价值; }
除去{CommandManager.RequerySuggested - =价值; }
}
公共无效执行(对象参数)
{
_execute(参数);
}
#endregion // ICommand的成员
}
此问题是一个星期,StackOverflow的报道多次。的问题是,每一个新的λ循环股内创建的相同的动作的变量。该lambda表达式不捕获的价值,他们捕捉到的变量。也就是说,当你说
名单,其中,作用>名单=新的名单,其中,作用>();
的foreach(INT的X范围(0,10))
list.Add(()=> {Console.WriteLine(X);});
清单[0]();
这当然版画10,因为这是x的值现在的。该行动是写x的当前值,而不是写x为创建委托时,后面的值。
要解决这个问题作出了新的变数:
名单,其中,作用>名单=新的名单,其中,作用>();
的foreach(INT的X范围(0,10))
{
INT Y = X;
list.Add(()=> {Console.WriteLine(Y);});
}
清单[0]();
由于这个问题是如此普遍,我们正在考虑改变C#的下一个版本,这样一个新的变量被通过foreach循环创建的每个时间。
请参阅的了解更多详情。
更新:从评论:
{方法= {无效< CreateCommands> b__0(System.Object的)}}
是的,当然它。该方法是每次相同。我想你是误会什么是委托创作的。看看这样的方式。假设你说:
VAR firstList =新名单≤)(; Func键< INT>&GT
{
()=大于10,()=→20
};
OK,我们有返回整数函数的列表。第一个返回10,第二个返回20
这是一样的:
静态INT ReturnTen(){返回10; }
静态INT ReturnTwenty(){返回20; }
...
VAR firstList =新的名单,其中,Func键< INT>>()
{ReturnTen,ReturnTwenty};
有道理这么远吗?现在我们添加您的foreach循环:
VAR secondList =新的名单,其中,Func键< INT>>();
的foreach(在firstList VAR FUNC)
secondList.Add(()=> FUNC());
确定有哪些呢的是的是什么意思?这意味着完全相同的事情:
类关闭
{
公共函数功能:LT; INT> FUNC;
公众诠释DoTheThing(){返回this.func(); }
}
...
VAR secondList =新的名单,其中,Func键< INT>>();
关闭关闭=新的闭包();
的foreach(在firstList VAR FUNC)
{
closure.func = FUNC;
secondList.Add(closure.DoTheThing);
}
现在是明确什么是怎么回事?通过循环每当你不创建一个新的闭包,你肯定不创建一个新的方法。你总是创建的委托指向同样的方法,始终以同样的关闭。
现在,如果不是你写
的foreach(在firstList VAR loopFunc)
{
VAR FUNC = loopFunc;
secondList.Add(FUNC);
}
那么code,我们会产生将
的foreach(在firstList VAR loopFunc)
{
VAR封=新的闭包();
closure.func = loopFunc;
secondList.Add(closure.DoTheThing);
}
现在列表中的每个新功能具有的相同的MethodInfo 的 - 它仍然是DoTheThing - 但一个的不同的封闭的
现在是否有意义,为什么你看到你的结果?
您可能还想阅读:
What是在C#中的lambda?
创建了一个委托的寿命另一个更新:从编辑的问题:
的foreach(在actionArray VAR行动)
{
动作<对象> executeHandler = O => { 行动(); };
commands.Add(新RelayCommand(executeHandler)); }
}
当然,这并没有帮助
。这有完全相同的问题和以前一样。的的问题是,在lambda被闭合在单可变'动作',而不是以上的行动的每个值。移动,其中的λ是明显创建围绕不能解决这个问题。你想要做的是创建一个新的变量的。你的第二个解决方案,以通过引用类型的字段分配一个新的变量这样做。你并不需要显式地做到这一点;正如我上面提到的编译器会做这样的你,如果你犯了一个新的变量内部的循环体。
要解决这个问题的正确的和短期的办法就是
的foreach(在actionArray VAR行动)
{
动作<对象>副本=行动;
commands.Add(新RelayCommand(X => {复制();}));
}
这样,你做的新的变量的通过循环每次都在循环中的每个拉姆达因此关闭过的不同的变量的。
每个代表将拥有的相同的MethodInfo 的,但一个的不同的封闭的。
您正在做的高阶函数式编程的程序。 您最好了解这些封锁和lambda表达式:如果你想拥有这样做正确的机会。没有时间像present。
In the following method I'm sending an enumeration of actions and want an array of ICommands back that call Action<object>
that wrap those actions (needed for the relayCommand).
The problem is that if I do this inside the for each (or even a for loop) I get commands that always execute the first action passed in the parameters.
public static ICommand[] CreateCommands(IEnumerable<Action> actions)
{
List<ICommand> commands = new List<ICommand>();
Action[] actionArray = actions.ToArray();
// works
//commands.Add(new RelayCommand(o => { actionArray[0](); })); // (_execute = {Method = {Void <CreateCommands>b__0(System.Object)}})
//commands.Add(new RelayCommand(o => { actionArray[1](); })); // (_execute = {Method = {Void <CreateCommands>b__1(System.Object)}})
foreach (var action in actionArray)
{
// always add the same _execute member for each RelayCommand (_execute = {Method = {Void <CreateCommands>b__0(System.Object)}})
commands.Add(new RelayCommand(o => { action(); }));
}
return commands.ToArray();
}
It seems that the lambda is always reused inside the loop thinking it does the same, but it does not.
How do I overcome this situation?How can i force the loop to threat o => { action(); }
always as a new one?
Thanks!
What I tried as per suggestions, but did not help:
foreach (var action in actionArray)
{
Action<object> executeHandler = o => { action(); };
commands.Add(new RelayCommand(executeHandler));
}
What seems to work for me is:
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));
}
Code of RelayCommand:
/// <summary>
/// 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'.
/// </summary>
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> 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
}
This problem is reported several times a week on StackOverflow. The problem is that each new lambda created inside the loop shares the same "action" variable. The lambdas do not capture the value, they capture the variable. That is, when you say
List<Action> list = new List<Action>();
foreach(int x in Range(0, 10))
list.Add( ()=>{Console.WriteLine(x);} );
list[0]();
that of course prints "10" because that's the value of x now. The action is "write the current value of x", not "write the value that x was back when the delegate was created."
To get around this problem make a new variable:
List<Action> list = new List<Action>();
foreach(int x in Range(0, 10))
{
int y = x;
list.Add( ()=>{Console.WriteLine(y);} );
}
list[0]();
Since this problem is so common we are considering changing the next version of C# so that a new variable gets created every time through the foreach loop.
See http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/ for more details.
UPDATE: From the comments:
{ Method = {Void <CreateCommands>b__0(System.Object)}}
Yes, of course it does. The method is the same every time. I think you are misunderstanding what a delegate creation is. Look at it this way. Suppose you said:
var firstList = new List<Func<int>>()
{
()=>10, ()=>20
};
OK, we have a list of functions that return ints. The first one returns 10, the second one returns 20.
This is just the same as:
static int ReturnTen() { return 10; }
static int ReturnTwenty() { return 20; }
...
var firstList = new List<Func<int>>()
{ ReturnTen, ReturnTwenty };
Make sense so far? Now we add your foreach loop:
var secondList = new List<Func<int>>();
foreach(var func in firstList)
secondList.Add(()=>func());
OK, what does that mean? That means exactly the same thing as:
class Closure
{
public Func<int> func;
public int DoTheThing() { return this.func(); }
}
...
var secondList = new List<Func<int>>();
Closure closure = new Closure();
foreach(var func in firstList)
{
closure.func = func;
secondList.Add(closure.DoTheThing);
}
Now is it clear what is going on here? Every time through the loop you do not create a new closure and you certainly do not create a new method. The delegate you create always points to the same method, and always to the same closure.
Now, if instead you wrote
foreach(var loopFunc in firstList)
{
var func = loopFunc;
secondList.Add(func);
}
then the code we would generate would be
foreach(var loopFunc in firstList)
{
var closure = new Closure();
closure.func = loopFunc;
secondList.Add(closure.DoTheThing);
}
Now each new function in the list has the same methodinfo -- it is still DoTheThing -- but a different closure.
Now does it make sense why you are seeing your result?
You might want to also read:
What is the lifetime of a delegate created by a lambda in C#?
ANOTHER UPDATE: From the edited question:
foreach (var action in actionArray)
{
Action<object> executeHandler = o => { action(); };
commands.Add(new RelayCommand(executeHandler)); }
}
Of course that did not help. That has exactly the same problem as before. The problem is that the lambda is closed over the single variable 'action' and not over each value of action. Moving around where the lambda is created obviously does not solve that problem. What you want to do is create a new variable. Your second solution does so by allocating a new variable by making a field of reference type. You don't need to do that explicitly; as I mentioned above the compiler will do so for you if you make a new variable interior to the loop body.
The correct and short way to fix the problem is
foreach (var action in actionArray)
{
Action<object> copy = action;
commands.Add(new RelayCommand(x=>{copy();}));
}
That way you make a new variable every time through the loop and each lambda in the loop therefore closes over a different variable.
Each delegate will have the same methodinfo but a different closure.
You're doing higher-order functional programming in your program. You'd better learn about "these closures and lambdas" if you want to have any chance of doing so correctly. No time like the present.
这篇关于与循环优化或lambda封闭问题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!