如何在匿名方法中产生回报?

时间:2011-03-23 21:08:55

标签: c# .net backgroundworker anonymous-methods yield-return

基本上我有一个匿名方法,我用于BackgroundWorker

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

当我这样做时,编译器告诉我:

  

“不能使用yield语句   在匿名方法或lambda中   表述“

所以在这种情况下,最优雅的方法是什么?顺便说一下,这个DoWork方法在静态方法中,以防对解决方案很重要。

7 个答案:

答案 0 :(得分:14)

不幸的是你不能。

编译器不允许您组合两个“神奇”的代码片段。两者都涉及重写代码以支持您想要做的事情:

  1. 通过将代码移动到适当的方法并使用该方法将局部变量提升到类的字段来完成匿名方法
  2. 迭代器方法被重写为状态机
  3. 但是,您可以重写代码以返回集合,因此在您的特定情况下,我会这样做:

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

    虽然事件(sender, e)看起来很奇怪,但它根本不会返回任何内容。你确定你为我们展示了一个真实的场景吗?


    修改好的,我想想我看到你在这里想做什么。

    您有一个静态方法调用,然后您想在后台执行代码,然后在后台调用完成后从该静态方法返回数据。

    这虽然可能,但不是一个好的解决方案,因为你有效地暂停一个线程等待另一个线程,这是在你暂停线程之前直接启动的。换句话说,你所做的只是增加上下文切换的开销。

    相反,您需要启动后台工作,然后在完成该工作后,处理结果数据。

答案 1 :(得分:10)

也许只返回linq表达式并延迟执行,如yield:

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

答案 2 :(得分:5)

除非我遗漏了某些东西,否则你无法做出你所要求的事情。

(我确实有一个答案,所以请仔细阅读我的解释,说明为什么你不能做你先做的事情,然后继续阅读。)

你的完整方法看起来像这样:

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

如果我们假设您的代码是&#34;合法&#34;然后在调用GetSomeValues时,即使将DoWork处理程序添加到worker,也不会执行lambda表达式,直到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();  
}

这段代码看起来有点复杂,但它只是将一个do work事件转换为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如何&#34;安排&#34;处理对观察者的订阅。

我希望这会有所帮助。

答案 3 :(得分:1)

DoWork属于DoWorkEventHandler类型,不返回任何内容(void), 所以在你的情况下根本不可能。

答案 4 :(得分:1)

worker应该设置DoWorkEventArgs的Result属性。

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

答案 5 :(得分: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 );
    } );

答案 6 :(得分:0)

对于新读者:在C#5中实现'匿名迭代器'(即嵌套在其他方法中)的最优雅方式可能类似于this cool trick with 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);

        }

C#7(Visual Studio 15 Preview中现已推出)支持local functions, which allow 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; }
        }
    }
}