如何在匿名方法中产生回报?
基本上我有一个匿名方法,我用于我的BackgroundWorker
:
worker.DoWork += ( sender, e ) => { foreach ( var effect in GlobalGraph.Effects ) { // Returns EffectResult yield return image.Apply (effect); } };
当我这样做时,编译器告诉我:
“yield语句不能在匿名方法或lambda表达式中使用”
那么在这种情况下,最优雅的方法是什么? 顺便说一句,这个DoWork方法在静态方法中,以防对解决方案很重要。
不幸的是你不能。
编译器不允许您组合两个“神奇”的代码片段。 两者都涉及重写代码以支持您要执行的操作:
- 通过将代码移动到适当的方法,并使用该方法将局部变量提升到类的字段来完成匿名方法
- 迭代器方法被重写为状态机
但是,您可以重写代码以返回集合,因此在您的特定情况下,我会这样做:
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如何“安排”对观察者的订阅处理。
我希望这有帮助。
DoWork
是DoWorkEventHandler
类型,它不返回任何内容( 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; } } } }