3

Listクラスがあり、オーバーライドGetEnumerator()して自分のEnumeratorクラスを返したいと思います。この列挙子クラスには、列挙子が使用されるときに更新される2つの追加のプロパティがあります。

CurrentIndex簡単にするために(これは正確なビジネスケースではありません)、これらのプロパティがとだったとしましょうRunningTotal

foreachループ内でこれらのプロパティを手動で管理することもできますが、この機能をカプセル化して再利用したいので、列挙子が適切な場所のようです。

問題: foreachはすべての列挙子ビジネスを非表示にするので、foreachステートメント内で現在の列挙子にアクセスしてプロパティを取得する方法はありますか?それとも、foreachし、厄介な古いwhileループを使用して、列挙子を自分で操作する必要がありますか?

4

3 に答える 3

4

厳密に言えば、あなたが言っていることを正確に実行したいのであれば、そうです、GetEnumeratorを呼び出し、whileループで列挙子を自分で制御する必要があります。

ビジネス要件についてあまり知らなくても、次のようなイテレータ関数を利用できる場合があります。

    public static IEnumerable<decimal> IgnoreSmallValues(List<decimal> list)
    {
        decimal runningTotal = 0M;
        foreach (decimal value in list)
        {
            // if the value is less than 1% of the running total, then ignore it
            if (runningTotal == 0M || value >= 0.01M * runningTotal)
            {
                runningTotal += value;
                yield return value;
            }
        }
    }

次に、これを行うことができます:

        List<decimal> payments = new List<decimal>() {
            123.45M,
            234.56M,
            .01M,
            345.67M,
            1.23M,
            456.78M
        };

        foreach (decimal largePayment in IgnoreSmallValues(payments))
        {
            // handle the large payments so that I can divert all the small payments to my own bank account.  Mwahaha!
        }

更新しました:

さて、これが私の「釣り針」ソリューションと呼んでいるもののフォローアップです。さて、私がこのように何かをする正当な理由を本当に考えることができないという免責事項を追加させてください、しかしあなたの状況は異なるかもしれません。

アイデアは、イテレータ関数に渡す「釣り針」オブジェクト(参照型)を作成するだけです。イテレータ関数は釣り針オブジェクトを操作します。外部のコードにはまだそれへの参照があるため、何が起こっているのかを可視化できます。

    public class FishingHook
    {
        public int Index { get; set; }
        public decimal RunningTotal { get; set; }
        public Func<decimal, bool> Criteria { get; set; }
    }

    public static IEnumerable<decimal> FishingHookIteration(IEnumerable<decimal> list, FishingHook hook)
    {
        hook.Index = 0;
        hook.RunningTotal = 0;
        foreach(decimal value in list)
        {
            // the hook object may define a Criteria delegate that
            // determines whether to skip the current value
            if (hook.Criteria == null || hook.Criteria(value))
            {
                hook.RunningTotal += value;
                yield return value;
                hook.Index++;
            }
        }
    }

あなたはそれをこのように利用するでしょう:

        List<decimal> payments = new List<decimal>() {
            123.45M,
            .01M,
            345.67M,
            234.56M,
            1.23M,
            456.78M
        };

        FishingHook hook = new FishingHook();

        decimal min = 0;
        hook.Criteria = x => x > min; // exclude any values that are less than/equal to the defined minimum
        foreach (decimal value in FishingHookIteration(payments, hook))
        {
            // update the minimum
            if (value > min) min = value;

            Console.WriteLine("Index: {0}, Value: {1}, Running Total: {2}", hook.Index, value, hook.RunningTotal);
        }
        // Resultint output is:
        //Index: 0, Value: 123.45, Running Total: 123.45
        //Index: 1, Value: 345.67, Running Total: 469.12
        //Index: 2, Value: 456.78, Running Total: 925.90
        // we've skipped the values .01, 234.56, and 1.23

基本的に、FishingHookオブジェクトを使用すると、イテレータの実行方法をある程度制御できます。質問から得た印象は、イテレータの内部動作にアクセスして、イテレータの途中でイテレータがどのように反復するかを操作できるようにする必要があるということでしたが、そうでない場合は、このソリューションが必要なものをやり過ぎてください。

于 2009-12-01T21:10:51.250 に答える
1

実際にforeach列挙子を取得することはできません - ただし、列挙子にそのデータを含むyieldタプルを返す ( ) ようにすることはできます。実際、おそらくLINQを使用してそれを行うことができます...

( LINQを使用してインデックスをきれいに取得できませんでした-Aggregateただし、を介して合計値と現在の値を取得できます。したがって、タプルアプローチは次のとおりです)

using System.Collections;
using System.Collections.Generic;
using System;
class MyTuple
{
    public int Value {get;private set;}
    public int Index { get; private set; }
    public int RunningTotal { get; private set; }
    public MyTuple(int value, int index, int runningTotal)
    {
        Value = value; Index = index; RunningTotal = runningTotal;
    }
    static IEnumerable<MyTuple> SomeMethod(IEnumerable<int> data)
    {
        int index = 0, total = 0;
        foreach (int value in data)
        {
            yield return new MyTuple(value, index++,
                total = total + value);
        }
    }
    static void Main()
    {
        int[] data = { 1, 2, 3 };
        foreach (var tuple in SomeMethod(data))
        {
            Console.WriteLine("{0}: {1} ; {2}", tuple.Index,
                tuple.Value, tuple.RunningTotal);
        }
    }
}
于 2009-12-01T21:03:07.253 に答える
0

要件に応じて、より機能的な方法でこのようなことを行うこともできます。あなたが求めているのは、複数のシーケンスをまとめて「圧縮」し、それらを一度に反復処理することです。あなたが与えた例の3つのシーケンスは次のようになります。

  1. 「値」シーケンス
  2. 「インデックス」シーケンス
  3. 「累計」シーケンス

次のステップは、これらの各シーケンスを個別に指定することです。

List<decimal> ValueList
var Indexes = Enumerable.Range(0, ValueList.Count)

最後の方が楽しいです...私が考えることができる2つの方法は、シーケンスを合計するために一時変数を使用するか、各アイテムの合計を再計算することです。2番目は明らかにパフォーマンスがはるかに低いため、一時的なものを使用したいと思います:

decimal Sum = 0;
var RunningTotals = ValueList.Select(v => Sum = Sum + v);

最後のステップは、これらをまとめて圧縮することです。.Net 4 にはZip 演算子が組み込まれています。この場合、次のようになります。

var ZippedSequence = ValueList.Zip(Indexes, (value, index) => new {value, index}).Zip(RunningTotals, (temp, total) => new {temp.value, temp.index, total});

これは、一緒に圧縮しようとするものが多いほど、明らかにノイズが多くなります。

最後のリンクには、Zip 関数を自分で実装するためのソースがあります。それは本当に単純な小さなコードです。

于 2009-12-01T22:11:10.433 に答える