8

遅延列挙オブジェクトを返す関数があるとします。

struct AnimalCount
{
    int Chickens;
    int Goats;
}

IEnumerable<AnimalCount> FarmsInEachPen()
{
    ....
    yield new AnimalCount(x, y);
    ....
}

また、2 つの別個の を消費する 2 つの関数もありますIEnumerable。次に例を示します。

ConsumeChicken(IEnumerable<int>);
ConsumeGoat(IEnumerable<int>);

どうすれば、a) ToList() を事前に変換せずに呼び出すことができますかConsumeChickenConsumeGoatこれFarmsInEachPen()は、2 つの無数のレコードがある可能性があるためです。b) マルチスレッドはありません。

基本的:

ConsumeChicken(FarmsInEachPen().Select(x => x.Chickens));
ConsumeGoats(FarmsInEachPen().Select(x => x.Goats));

しかし、二重列挙を強制することなく。

マルチスレッドで解決できますが、各リストのバッファ キューで不必要に複雑になります。

だから私は完全に評価せずにAnimalCount列挙子を2つの列挙子に分割する方法を探しています。ロックステップで一緒に実行しても問題ありません。intAnimalCountConsumeGoatConsumeChicken

すぐに解決策を感じることができますが、まだそこにはいません。IEnumerable私は、フィードされたものを返すヘルパー関数の方針に沿って考えていConsumeChickenます。イテレータが使用されるたびに、内部的に が呼び出されConsumeGoat、2 つの関数がロックステップで実行されます。もちろん、私は何ConsumeGoat度も電話したくありません..

4

4 に答える 4

4

ConsumeChickens(IEnumerable<int>)とが順番に呼び出され、それぞれが個別にリストを列挙しているため、あなたが望むことを行う方法はないと思いますConsumeGoats(IEnumerable<int>)-リストの2つの個別の列挙なしでどのように機能すると思いますか?

状況に応じて、メソッドConsumeChicken(int)ConsumeGoat(int)メソッド(それぞれが1 つのアイテムを消費する) を用意し、それらを交互に呼び出すことをお勧めします。このような:

foreach(var animal in animals)
{
    ConsomeChicken(animal.Chickens);
    ConsomeGoat(animal.Goats);
}

animalsこれにより、コレクションが 1 回だけ列挙されます。


また、注意: LINQ プロバイダーと、正確に何をしようとしているのかによっては、より良いオプションがあるかもしれません。たとえば、linq-to-sql または linq-to-entities を使用してデータベースからニワトリとヤギの両方の合計を取得しようとしている場合、次のクエリ..

from a in animals
group a by 0 into g
select new 
{
    TotalChickens = g.Sum(x => x.Chickens), 
    TotalGoats = g.Sum(x => x.Goats)
}

単一のクエリになり、データベース側で合計を実行します。これは、テーブル全体をプルしてクライアント側で合計を実行するよりもはるかに望ましい方法です。

于 2013-03-28T20:01:37.097 に答える
2

あなたが問題を提起した方法では、これを行う方法はありません。 IEnumerable<T>つまり、シーケンスの先頭に移動してから、「次のアイテムをください」( / ) と繰り返し尋ねることができます。1 つのスレッドで、とから同時に 2 つの異なるものを引き出すことはできません。1 つを実行してからもう 1 つ実行する必要があります (2 番目を具体化する必要があります)。GetEnumeratorMoveNextCurrentanimals.Select(a => a.Chickens)animals.Select(a => a.Goats)

BlueRaja が行った提案は、問題をわずかに変更する 1 つの方法です。その道を行くことをお勧めします。

もう 1 つの方法は、プッシュ列挙型IObservable<T>である Microsoft のリアクティブ拡張機能 (Rx)を利用することです。それをどのように行うかについては詳しく説明しませんが、調べることはできます。

編集:

上記は、ConsumeChickensConsumeGoatsの両方が戻ってvoidいるか、少なくともIEnumerable<T>自分自身を返していないことを前提としています - これは明らかな前提のようです。足の不自由な人が実際にコメントしてくれれば幸いです。

于 2013-03-28T20:11:37.883 に答える
2

FarmsInEachPen の戻り値を変換してコレクションまたは IObservable をプッシュし、それを操作するために ReactiveExtensions を使用することを実際に達成する簡単な方法

var observable = new Subject<Animals>()
observable.Do(x=> DoSomethingWithChicken(x. Chickens))
observable.Do(x=> DoSomethingWithGoat(x.Goats))

foreach(var item in FarmsInEachPen())
{
    observable.OnNext(item)
}   
于 2013-03-28T21:21:57.207 に答える
1

@Leeが私に与えてくれた道のおかげで、私はそれを理解しました。

2 つの zip 間で 1 つの列挙子を共有し、アダプター関数を使用して正しい要素をシーケンスに射影する必要があります。

private static IEnumerable<object> ConsumeChickens(IEnumerable<int> xList)
{
    foreach (var x in xList)
    {
        Console.WriteLine("X: " + x);
        yield return null;
    }
}

private static IEnumerable<object> ConsumeGoats(IEnumerable<int> yList)
{
    foreach (var y in yList)
    {
        Console.WriteLine("Y: " + y);
        yield return null;
    }
}

private static IEnumerable<int> SelectHelper(IEnumerator<AnimalCount> enumerator, int i)
{
    bool c = i != 0 || enumerator.MoveNext();
    while (c)
    {
        if (i == 0)
        {
            yield return enumerator.Current.Chickens;
            c = enumerator.MoveNext();
        }
        else
        {
            yield return enumerator.Current.Goats;
        }
    }
}

private static void Main(string[] args)
{
    var enumerator = GetAnimals().GetEnumerator();

    var chickensList = ConsumeChickens(SelectHelper(enumerator, 0));
    var goatsList = ConsumeGoats(SelectHelper(enumerator, 1));

    var temp = chickensList.Zip(goatsList, (i, i1) => (object) null);
    temp.ToList();

    Console.WriteLine("Total iterations: " + iterations);
}
于 2013-03-28T20:21:55.793 に答える