c # foreach делегат XAMARIN ANDROID [дубликат]

Попробуйте

  p = re.compile ('a {1} b {1,3}')  

... и учтите пространство.

164
задан Peter Mortensen 17 January 2014 в 20:43
поделиться

7 ответов

Да - возьмите копию переменной внутри цикла:

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, а моя статья о замыканиях еще несколько примеров.

149
ответ дан Jon Skeet 16 August 2018 в 07:59
поделиться
  • 1
    В книге Джона также есть хорошая глава об этом (перестань быть смиренным, Джон!) – Marc Gravell♦ 7 November 2008 в 08:57
  • 2
    Это выглядит лучше, если я позволяю другим людям подключаться к нему;) (Признаюсь, что я, как правило, проголосую за рекомендации, рекомендующие это.) – Jon Skeet 7 November 2008 в 09:03
  • 3
    Как всегда, отзывы на skeet@pobox.com были бы оценены :) – Jon Skeet 7 November 2008 в 10:30
  • 4
    Для поведения C # 5.0 другое (более разумное) см. Более новый ответ Jon Skeet - stackoverflow.com/questions/16264289/… – Alexei Levenkov 22 January 2016 в 03:35

Да, вам нужна область видимости 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();
4
ответ дан cfeduke 16 August 2018 в 07:59
поделиться

Это не имеет никакого отношения к циклам.

Это поведение срабатывает, потому что вы используете лямбда-выражение () => 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» не являются концепцией, связанной с циклами, но скорее, к анонимным методам / лямбда-выражениям используют локальные переменные с областью - хотя некоторые неосторожное использование циклов демонстрируют закрытие ловушек.

1
ответ дан David Refaeli 16 August 2018 в 07:59
поделиться

То же самое происходит в многопоточном режиме (C #, .NET 4.0].

См. следующий код:

Цель печати 1,2,3,4,5 в порядке.

for (int counter = 1; counter <= 5; counter++)
{
    new Thread (() => Console.Write (counter)).Start();
}

Результат интересный! (Это может быть как 21334 ...)

Единственное решение - использовать локальные переменные.

for (int counter = 1; counter <= 5; counter++)
{
    int localVar= counter;
    new Thread (() => Console.Write (localVar)).Start();
}
2
ответ дан Peter Mortensen 16 August 2018 в 07:59
поделиться
  • 1
    Мне это, похоже, не помогает. Все еще не детерминирован. – Mladen Mihajlovic 31 January 2014 в 13:14

За кулисами компилятор генерирует класс, который представляет собой замыкание для вызова метода. Он использует этот единственный экземпляр класса закрытия для каждой итерации цикла. Код выглядит примерно так: это облегчает просмотр ошибки:

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

На самом деле это не скомпилированный код из вашего примера, но я изучил свой собственный код и этот очень похоже на то, что на самом деле генерирует компилятор.

8
ответ дан sharptooth 16 August 2018 в 07:59
поделиться

Я считаю, что то, что вы испытываете, - это что-то известное как Closure http://en.wikipedia.org/wiki/Closure_ (computer_science) . Ваша lamba имеет ссылку на переменную, которая находится вне самой функции. Ваш lamba не интерпретируется до тех пор, пока вы его не вызовите, и как только он получит значение, которое имеет переменная во время выполнения.

16
ответ дан TheCodeJunkie 16 August 2018 в 07:59
поделиться

Способ сохранения значения, необходимого вам в прокси-переменной, и получить эту переменную.

I.E.

while( variable < 5 )
{
    int copy = variable;
    actions.Add( () => copy * 2 );
    ++variable;
}
7
ответ дан tjlevine 16 August 2018 в 07:59
поделиться
  • 1
    Да, это работает. Но почему? – Morgan Cheng 7 November 2008 в 08:34
  • 2
    См. Объяснение в моем отредактированном ответе. Сейчас я нахожу соответствующий бит спецификации. – Jon Skeet 7 November 2008 в 08:35
  • 3
    Ха-ха-Джон, я на самом деле просто прочитал вашу статью: csharpindepth.com/Articles/Chapter5/Closures.aspx Ты хорошо справляешься с моим другом. – tjlevine 7 November 2008 в 08:36
  • 4
    @tjlevine: Большое спасибо. Я добавлю ссылку на это в своем ответе. Я забыл об этом! – Jon Skeet 7 November 2008 в 08:37
  • 5
    Кроме того, Джон, я бы хотел прочитать ваши мысли о различных предложениях по закрытию Java 7. Я видел, как вы упоминаете, что хотите написать одно, но я этого не видел. – tjlevine 7 November 2008 в 08:42
Другие вопросы по тегам:

Похожие вопросы: