Подтвердить что ты не робот

Как вернуть доход внутри анонимных методов?

В принципе у меня есть анонимный метод, который я использую для моего BackgroundWorker:

worker.DoWork += ( sender, e ) =>
{
    foreach ( var effect in GlobalGraph.Effects )
    {
        // Returns EffectResult
        yield return image.Apply (effect);
    }
};

Когда я это сделаю, компилятор говорит мне:

"Оператор yield нельзя использовать внутри анонимного метода или лямбда Выражение"

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

4b9b3361

Ответ 1

Итак, я сделал что-то вроде этого, которое делает то, что я хотел (некоторые переменные опущены):

public static void Run ( Action<float, EffectResult> action )
{
    worker.DoWork += ( sender, e ) =>
    {
        foreach ( var effect in GlobalGraph.Effects )
        {
            var result = image.Apply (effect);

            action (100 * ( index / count ), result );
        }
    }
};

а затем на сайте вызова:

GlobalGraph.Run ( ( p, r ) =>
    {
        this.Progress = p;
        this.EffectResults.Add ( r );
    } );

Ответ 2

К сожалению, вы не можете.

Компилятор не позволяет комбинировать два "волшебных" фрагмента кода. Оба варианта включают переписывание кода для поддержки того, что вы хотите сделать:

  • Анонимный метод выполняется путем перемещения кода до надлежащего метода и подъема локальных переменных в поля класса с помощью этого метода
  • Метод итератора переписан как конечный автомат

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

worker.DoWork += ( sender, e ) =>
{
    return GlobalGraph.Effects
        .Select(effect => image.Apply(effect));
};

хотя для события (sender, e) выглядит странно, чтобы вообще что-то возвращать. Вы уверены, что у вас есть реальный сценарий для нас?


Изменить Хорошо, я думаю. Я вижу, что вы пытаетесь сделать здесь.

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

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

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

Ответ 3

Возможно, просто верните выражение linq и отложите выполнение как yield:

return GlobalGraph.Effects.Select(x => image.Apply(x));

Ответ 4

Если мне что-то не хватает, вы не можете делать то, что вы просите.

(У меня есть ответ для вас, поэтому, пожалуйста, прочитайте мое объяснение, почему вы не можете делать то, что делаете первым, а затем читайте дальше.)

Вы полный метод выглядели бы примерно так:

public static IEnumerable<EffectResult> GetSomeValues()
{
    // code to set up worker etc
    worker.DoWork += ( sender, e ) =>
    {
        foreach ( var effect in GlobalGraph.Effects )
        {
            // Returns EffectResult
            yield return image.Apply (effect);
        }
    };
}

Если мы предположим, что ваш код был "законным", тогда, когда вызывается GetSomeValues, хотя обработчик DoWork добавляется в worker, выражение лямбда не выполняется до тех пор, пока не будет запущено событие DoWork, Таким образом, вызов GetSomeValues завершается, не возвращая никаких результатов, и lamdba может или не может быть вызван на более позднем этапе, что в любом случае слишком поздно для вызывающего метода GetSomeValues.

Лучший ответ на использование Rx.

Rx поворачивается на голову IEnumerable<T>. Вместо того, чтобы запрашивать значения из перечислимого, Rx имеет значения, которые вы выбрали из IObservable<T>.

Поскольку вы используете фонового работника и отвечаете на событие, у вас фактически есть уже перенесенные вами значения. С Rx вам будет легко делать то, что вы пытаетесь сделать.

У вас есть несколько вариантов. Вероятно, самым простым является это:

public static IObservable<IEnumerable<EffectResult>> GetSomeValues()
{
    // code to set up worker etc
    return from e in Observable.FromEvent<DoWorkEventArgs>(worker, "DoWork")
           select (
               from effect in GlobalGraph.Effects
               select image.Apply(effect)
           );
}

Теперь вызывающие ваши методы GetSomeValues будут делать это:

GetSomeValues().Subscribe(ers =>
{
    foreach (var er in ers)
    {
        // process each er
    }
});

Если вы знаете, что DoWork срабатывает только один раз, этот подход может быть немного лучше:

public static IObservable<EffectResult> GetSomeValues()
{
    // code to set up worker etc
    return Observable
        .FromEvent<DoWorkEventArgs>(worker, "DoWork")
        .Take(1)
        .Select(effect => from effect in GlobalGraph.Effects.ToObservable()
                          select image.Apply(effect))
        .Switch();  
}

Этот код выглядит несколько более сложным, но он просто превращает одно рабочее событие в поток объектов EffectResult.

Затем вызывающий код выглядит так:

GetSomeValues().Subscribe(er =>
{
    // process each er
});

Rx можно даже использовать для замены рабочего фона. Это может быть наилучшим вариантом для вас:

public static IObservable<EffectResult> GetSomeValues()
{
    // set up code etc
    return Observable
        .Start(() => from effect in GlobalGraph.Effects.ToObservable()
                     select image.Apply(effect), Scheduler.ThreadPool)
        .Switch();  
}

Код вызова такой же, как в предыдущем примере. Scheduler.ThreadPool сообщает Rx, как "планировать" обработку подписки на наблюдателя.

Надеюсь, это поможет.

Ответ 5

DoWork имеет тип DoWorkEventHandler, который ничего не возвращает (void), так что это вообще не возможно в вашем случае.

Ответ 6

Работник должен установить свойство Result для DoWorkEventArgs.

worker.DoWork += (s, e) => e.Result = GlobalGraph.Effects.Select(x => image.Apply(x));

Ответ 7

Для новых читателей: самый элегантный способ реализовать "анонимные итераторы" (т.е. вложенные в другие методы) в С# 5, вероятно, что-то вроде этот классный трюк с async/await (не путайте эти ключевые слова, приведенный ниже код вычисляется абсолютно синхронно - см. подробности на связанной странице):

        public IEnumerable<int> Numbers()
        {
            return EnumeratorMonad.Build<int>(async Yield =>
            {
                await Yield(11);
                await Yield(22);
                await Yield(33);
            });
        }

        [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod]
        public void TestEnum()
        {
            var v = Numbers();
            var e = v.GetEnumerator();

            int[] expected = { 11, 22, 33 };

            Numbers().Should().ContainInOrder(expected);

        }

С# 7 (доступно сейчас в Visual Studio 15 Preview) поддерживает локальные функции, которые позволяют yield return:

public IEnumerable<T> Filter<T>(IEnumerable<T> source, Func<T, bool> filter)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (filter == null) throw new ArgumentNullException(nameof(filter));

    return Iterator();

    IEnumerable<T> Iterator()
    {
        foreach (var element in source) 
        {
            if (filter(element)) { yield return element; }
        }
    }
}