Вы можете скомпилировать регулярное выражение с флажком VERBOSE, это означает, что большинство пробелов в регулярном выражении будут игнорироваться. Я думаю, что это очень хорошая практика для описания сложных регулярных выражений более читаемым образом.
Подробнее см. [D0] здесь ...
Надеемся, что это поможет ...
Да - возьмите копию переменной внутри цикла:
while (variable < 5)
{
int copy = variable;
actions.Add(() => copy * 2);
++ variable;
}
Вы можете думать об этом, как если бы компилятор C # создавал «новую» локальную переменную каждый раз, когда он попадает в объявление переменной. На самом деле он создаст соответствующие новые объекты закрытия, и он становится сложным (с точки зрения реализации), если вы ссылаетесь на переменные в нескольких областях, но он работает:)
Обратите внимание, что более распространенное появление этого проблема заключается в использовании for
или foreach
:
for (int i=0; i < 10; i++) // Just one variable
foreach (string x in foo) // And again, despite how it reads out loud
Подробнее об этом см. в разделе 7.14.4.2 спецификации C # 3.0, а моя статья о замыканиях еще несколько примеров.
Да, вам нужна область видимости variable
в цикле и передать ее лямбда таким образом:
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
int variable1 = variable;
actions.Add(() => variable1 * 2);
++variable;
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
Console.ReadLine();
Это поведение срабатывает, потому что вы используете лямбда-выражение () => variable * 2
, где внешняя область variable
фактически не определена во внутренней области лямбда.
Лямбда-выражения (в C # 3 +, а также анонимные методы в C # 2) по-прежнему создают реальные методы. Передача переменных этим методам связана с некоторыми дилеммами (передать по значению? Pass по ссылке? C # идет по ссылке - но это открывает еще одну проблему, когда ссылка может пережить реальную переменную). Что C # для решения всех этих дилемм заключается в создании нового вспомогательного класса («замыкание») с полями, соответствующими локальным переменным, используемым в лямбда-выражениях, и методам, соответствующим фактическим лямбда-методам. Любые изменения в variable
в вашем коде фактически преобразуются для изменения в этом ClosureClass.variable
. Таким образом, ваш цикл while обновляет ClosureClass.variable
до тех пор, пока он не достигнет 10, тогда вы для циклов выполняете действия, которые все работают на одном и том же ClosureClass.variable
.
Чтобы получить ожидаемый результат, вам необходимо создать разделение между переменной цикла и переменной, которая закрывается. Вы можете сделать это, введя другую переменную, то есть:
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
var t = variable; // now t will be closured (i.e. replaced by a field in the new class)
actions.Add(() => t * 2);
++variable; // changing variable won't affect the closured variable t
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
Вы также можете переместить замыкание на другой метод для создания этого разделения:
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
actions.Add(Mult(variable));
++variable;
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
Вы можете реализовать Mult как лямбда-выражение (неявное замыкание)
static Func<int> Mult(int i)
{
return () => i * 2;
}
или с фактическим вспомогательным классом:
public class Helper
{
public int _i;
public Helper(int i)
{
_i = i;
}
public int Method()
{
return _i * 2;
}
}
static Func<int> Mult(int i)
{
Helper help = new Helper(i);
return help.Method;
}
В любом случае «Closures» не являются концепцией, связанной с циклами, но скорее, к анонимным методам / лямбда-выражениям используют локальные переменные с областью - хотя некоторые неосторожное использование циклов демонстрируют закрытие ловушек.
За кулисами компилятор генерирует класс, который представляет собой замыкание для вызова метода. Он использует этот единственный экземпляр класса закрытия для каждой итерации цикла. Код выглядит примерно так: это облегчает просмотр ошибки:
void Main()
{
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
var closure = new CompilerGeneratedClosure();
Func<int> anonymousMethodAction = null;
while (closure.variable < 5)
{
if(anonymousMethodAction == null)
anonymousMethodAction = new Func<int>(closure.YourAnonymousMethod);
//we're re-adding the same function
actions.Add(anonymousMethodAction);
++closure.variable;
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
}
class CompilerGeneratedClosure
{
public int variable;
public int YourAnonymousMethod()
{
return this.variable * 2;
}
}
На самом деле это не скомпилированный код из вашего примера, но я изучил свой собственный код и этот очень похоже на то, что на самом деле генерирует компилятор.
Я считаю, что то, что вы испытываете, - это что-то известное как Closure http://en.wikipedia.org/wiki/Closure_ (computer_science) . Ваша lamba имеет ссылку на переменную, которая находится вне самой функции. Ваш lamba не интерпретируется до тех пор, пока вы его не вызовите, и как только он получит значение, которое имеет переменная во время выполнения.
Способ сохранения значения, необходимого вам в прокси-переменной, и получить эту переменную.
I.E.
while( variable < 5 )
{
int copy = variable;
actions.Add( () => copy * 2 );
++variable;
}