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

Joa*_*nge 31 .net c# anonymous-methods backgroundworker yield-return

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

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

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

"yield语句不能在匿名方法或lambda表达式中使用"

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

ang*_*son 16

不幸的是你不能.

编译器不允许您组合两个"神奇"的代码片段.两者都涉及重写代码以支持您要执行的操作:

  1. 通过将代码移动到适当的方法,并使用该方法将局部变量提升到类的字段来完成匿名方法
  2. 迭代器方法被重写为状态机

但是,您可以重写代码以返回集合,因此在您的特定情况下,我会这样做:

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

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


编辑好的,我我看到你在这里想做什么.

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

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

相反,您需要开始后台工作,然后在完成该工作后,处理结果数据.


Tej*_*ejs 10

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

return GlobalGraph.Effects.Select(x => image.Apply(x));
Run Code Online (Sandbox Code Playgroud)


Eni*_*ity 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);
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

如果我们假设您的代码是"合法的"那么在GetSomeValues调用时,即使DoWork添加了处理程序worker,在DoWork触发事件之前不会执行lambda表达式.因此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)
           );
}
Run Code Online (Sandbox Code Playgroud)

现在你的GetSomeValues方法的调用者会这样做:

GetSomeValues().Subscribe(ers =>
{
    foreach (var er in ers)
    {
        // process each er
    }
});
Run Code Online (Sandbox Code Playgroud)

如果你知道这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();  
}
Run Code Online (Sandbox Code Playgroud)

这段代码看起来有点复杂,但它只是将单个do work事件转换为EffectResult对象流.

然后调用代码如下所示:

GetSomeValues().Subscribe(er =>
{
    // process each er
});
Run Code Online (Sandbox Code Playgroud)

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();  
}
Run Code Online (Sandbox Code Playgroud)

调用代码与前一个示例相同.该Scheduler.ThreadPool告诉的Rx如何"安排"订阅观察者的处理.

我希望这有帮助.


Joa*_*nge 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 );
        }
    }
};
Run Code Online (Sandbox Code Playgroud)

然后在调用站点中:

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