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

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

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

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

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

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

不幸的是你不能。

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

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

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

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

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


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

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

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

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

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

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

除非我遗漏了什么,否则你不能做你所要求的。

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

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

 public static IEnumerable 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 ,也不会执行lambda表达式,直到触发DoWork事件。 因此,对GetSomeValues的调用完成后不会返回任何结果,并且lamdba可能会或者可能不会在稍后阶段调用 – 这对于GetSomeValues方法的调用者来说太迟了。

你最好的答案是使用Rx 。

Rx将IEnumerable置于其头上。 Rx不是从可枚举中请求值,而是从IObservable推送给您的值。

由于您正在使用后台工作程序并响应事件,因此您实际上已将值推送给您。 使用Rx,您可以轻松完成您想要做的事情。

你有几个选择。 可能最简单的方法是这样做:

 public static IObservable> GetSomeValues() { // code to set up worker etc return from e in Observable.FromEvent(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 GetSomeValues() { // code to set up worker etc return Observable .FromEvent(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 GetSomeValues() { // set up code etc return Observable .Start(() => from effect in GlobalGraph.Effects.ToObservable() select image.Apply(effect), Scheduler.ThreadPool) .Switch(); } 

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

我希望这有帮助。

DoWorkDoWorkEventHandler类型,它不返回任何内容( void ),因此在您的情况下根本不可能。

worker应该设置DoWorkEventArgs的Result属性。

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

好的,所以我做了这样的事情,做了我想做的事情(省略了一些变量):

 public static void Run ( Action 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 ); } ); 

对于新读者:在C#5中实现“匿名迭代器”(即嵌套在其他方法中)的最优雅方式可能就像使用async / await这样的酷技巧 (不要被这些关键字混淆,下面的代码是绝对同步计算 – 查看链接页面中的详细信息):

  public IEnumerable Numbers() { return EnumeratorMonad.Build(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中可用)支持本地函数,它允许yield return

 public IEnumerable Filter(IEnumerable source, Func filter) { if (source == null) throw new ArgumentNullException(nameof(source)); if (filter == null) throw new ArgumentNullException(nameof(filter)); return Iterator(); IEnumerable Iterator() { foreach (var element in source) { if (filter(element)) { yield return element; } } } }