web-dev-qa-db-ja.com

匿名メソッド内でリターンを生成する方法は?

基本的に、BackgroundWorkerに使用する匿名メソッドがあります。

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

これを行うと、コンパイラは次のように指示します。

「yieldステートメントは無名メソッドまたはラムダ式内では使用できません」

したがって、この場合、これを行うための最もエレガントな方法は何ですか?ところで、このDoWorkメソッドは、ソリューションにとって重要な場合に備えて、静的メソッドの内部にあります。

31
Joan Venge

さて、私は私が望むことをするこのようなことをしました(いくつかの変数は省略されています):

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 );
    } );
1
Joan Venge

残念ながらできません。

コンパイラでは、2つの「魔法の」コードを組み合わせることができません。どちらも、やりたいことをサポートするようにコードを書き直す必要があります。

  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));
10
Tejs

私が何かを逃していない限り、あなたはあなたが求めていることをすることはできません。

(私はあなたに答えを持っているので、あなたが最初にやっていることをできない理由についての私の説明を過ぎて読んでから、読んでください。)

完全なメソッドは次のようになります。

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が呼び出されると、ラムダ式は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が1回だけ起動することがわかっている場合は、このアプローチの方が少し優れている可能性があります。

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

このコードはもう少し複雑に見えますが、単一のdoworkイベントを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に指示します。

これがお役に立てば幸いです。

5
Enigmativity

DoWork はタイプDoWorkEventHandlerで、何も返さない(void)ので、あなたの場合はまったく不可能です。

1
manji

ワーカーは、DoWorkEventArgsのResultプロパティを設定する必要があります。

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

新しい読者の場合: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; }
        }
    }
}
0
user1414213562