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

发布于 2024-10-26 16:38:31 字数 424 浏览 5 评论 0原文

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

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

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

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

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

Basically I have an anonymous method that I use for my BackgroundWorker:

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

When I do this the compiler tells me:

"The yield statement cannot be used
inside an anonymous method or lambda
expression"

So in this case, what's the most elegant way to do this? Btw this DoWork method is inside a static method, in case that matters for the solution.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

つ低調成傷 2024-11-02 16:38:31

不幸的是你不能。

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

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

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

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

尽管事件 (sender, e) 返回任何内容看起来很奇怪。您确定您正在向我们展示真实的场景吗?


编辑好的,我认为我明白你想在这里做什么。

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

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

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

Unfortunately you can't.

The compiler does not allow you to combine the two "magic" pieces of code. Both involve rewriting your code to support what you want to do:

  1. An anonymous method is done by moving the code to a proper method, and lifting local variables to fields on the class with that method
  2. An iterator method is rewritten as a state machine

You can, however, rewrite the code to return the collection, so in your particular case I would do this:

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

though it looks odd for an event (sender, e) to return anything at all. Are you sure you're showing a real scenario for us?


Edit Ok, I think I see what you're trying to do here.

You have a static method call, and then you want to execute code in the background, and then return data from that static method once the background call completes.

This is, while possible, not a good solution since you're effectively pausing one thread to wait for another, that was started directly before you paused the thread. In other words, all you're doing is adding overhead of context switching.

Instead you need to just kick off the background work, and then when that work is completed, process the resulting data.

盗心人 2024-11-02 16:38:31

也许只是返回 linq 表达式并像 Yield 一样推迟执行:

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

Perhaps just return the linq expression and defer execution like yield:

return GlobalGraph.Effects.Select(x => image.Apply(x));
被你宠の有点坏 2024-11-02 16:38:31

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

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

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

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 中,lambda 表达式也不会执行,直到DoWork 事件被触发。因此,对 GetSomeValues 的调用完成时不会返回任何结果,并且 lamdba 可能会也可能不会在稍后阶段被调用 - 这对于 GetSomeValues 方法的调用者来说为时已晚反正。

最好的答案是使用

Rx 彻底改变了 IEnumerable。 Rx 不是从可枚举请求值,而是从 IObservable推送值。

由于您正在使用后台工作人员并响应事件,因此您实际上已经将值推送给您了。有了 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 如何“调度”观察者订阅的处理。

我希望这有帮助。

Unless I'm missing something, you can't do what you're asking.

(I do have an answer for you, so please read past my explanation of why you can't do what you're doing first, and then read on.)

You full method would look something like this:

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

If we assume that your code was "legal" then when GetSomeValues is called, even though the DoWork handler is added to worker, the lambda expression isn't executed until the DoWork event is fired. So the call to GetSomeValues completes without returning any results and the lamdba may or may not get called at a later stage - which is then too late for the caller of the GetSomeValues method anyway.

Your best answer is to the use Rx.

Rx turns IEnumerable<T> on its head. Instead of requesting values from an enumerable, Rx has values pushed to you from an IObservable<T>.

Since you're using a background worker and responding to an event you are effectively having the values pushed to you already. With Rx it becomes easy to do what you're trying to do.

You have a couple of options. Probably the simplest is to do this:

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

Now callers of your GetSomeValues method would do this:

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

If you know that DoWork is only going to fire once, then this approach might be a little better:

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

This code looks a little more complicated, but it just turns a single do work event into a stream of EffectResult objects.

Then the calling code looks like this:

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

Rx can even be used to replace the background worker. This might be the best option for you:

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

The calling code is the same as the previous example. The Scheduler.ThreadPool tells Rx how to "schedule" the processing of subscriptions to the observer.

I hope this helps.

生寂 2024-11-02 16:38:31

对于新读者:在 C#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);

        }

C#7(现已在 Visual Studio 15 预览版中提供)支持 本地函数,允许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; }
        }
    }
}

For new readers: the most elegant way to implement 'anonymous iterators' (i. e. nested in other methods) in C#5 is probably something like this cool trick with async/await (don't be confused by these keywords, the code below is computed absolutely synchronously - see details in the linked page):

        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 (available now in Visual Studio 15 Preview) supports 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; }
        }
    }
}
天煞孤星 2024-11-02 16:38:31

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

DoWork is of type DoWorkEventHandler which returns nothing (void),
so it's not possible at all in your case.

阳光的暖冬 2024-11-02 16:38:31

工作人员应设置 DoWorkEventArgs 的 Result 属性。

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

The worker should set the Result property of DoWorkEventArgs.

worker.DoWork += (s, e) => e.Result = GlobalGraph.Effects.Select(x => image.Apply(x));
哽咽笑 2024-11-02 16:38:31

好吧,我做了类似的事情,它做了我想要的事情(省略了一些变量):

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

Ok so I did something like this which does what I wanted (some variables omitted):

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

and then in the call site:

GlobalGraph.Run ( ( p, r ) =>
    {
        this.Progress = p;
        this.EffectResults.Add ( r );
    } );
我做我的改变 2024-11-02 16:38:31

我想用 ForEachMonad 的实现来补充 user1414213562 的答案

static class ForEachMonad
{
    public static IEnumerable<A> Lift<A>(A a) { yield return a; }

    // Unfortunately, this doesn't compile

    // public static Func<IEnumerable<A>, IEnumerable<B>> Lift<A, B>(Func<A, IEnumerable<B>> f) =>
    //     (IEnumerable<A> ea) => { foreach (var a in ea) { foreach (var b in f(a)) { yield return b; } } }

    // Fortunately, this does :)
    
    public static Func<IEnumerable<A>, IEnumerable<B>> Lift<A, B>(Func<A, IEnumerable<B>> f)
    {
        IEnumerable<B> lift(IEnumerable<A> ea)
        {
            foreach (var a in ea) { foreach (var b in f(a)) { yield return b; } }
        }
        return lift;
    }

    public static void Demo()
    {
        var f = (int x) => (IEnumerable<int>)new int[] { x + 1, x + 2, x + 3 };
        var g = (int x) => (IEnumerable<double>)new double[] { Math.Sqrt(x), x*x };
        var log = (double d) => { Console.WriteLine(d); return Lift(d); };

        var e1 = Lift(0);
        var e2 = Lift(f)(e1);
        var e3 = Lift(g)(e2);
        // we call ToArray in order to materialize the IEnumerable
        Lift(log)(e3).ToArray();
    }
}

运行 ForEachMonad.Demo() 会产生以下输出:

1
1
1,4142135623730951
4
1,7320508075688772
9

I wanted to supplement user1414213562's answer with an implementation of the ForEachMonad.

static class ForEachMonad
{
    public static IEnumerable<A> Lift<A>(A a) { yield return a; }

    // Unfortunately, this doesn't compile

    // public static Func<IEnumerable<A>, IEnumerable<B>> Lift<A, B>(Func<A, IEnumerable<B>> f) =>
    //     (IEnumerable<A> ea) => { foreach (var a in ea) { foreach (var b in f(a)) { yield return b; } } }

    // Fortunately, this does :)
    
    public static Func<IEnumerable<A>, IEnumerable<B>> Lift<A, B>(Func<A, IEnumerable<B>> f)
    {
        IEnumerable<B> lift(IEnumerable<A> ea)
        {
            foreach (var a in ea) { foreach (var b in f(a)) { yield return b; } }
        }
        return lift;
    }

    public static void Demo()
    {
        var f = (int x) => (IEnumerable<int>)new int[] { x + 1, x + 2, x + 3 };
        var g = (int x) => (IEnumerable<double>)new double[] { Math.Sqrt(x), x*x };
        var log = (double d) => { Console.WriteLine(d); return Lift(d); };

        var e1 = Lift(0);
        var e2 = Lift(f)(e1);
        var e3 = Lift(g)(e2);
        // we call ToArray in order to materialize the IEnumerable
        Lift(log)(e3).ToArray();
    }
}

Running ForEachMonad.Demo() produces the following output:

1
1
1,4142135623730951
4
1,7320508075688772
9
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文