在 foreach 循环中访问枚举器?

发布于 2024-08-13 16:50:39 字数 404 浏览 8 评论 0原文

我有一个 List 类,我想重写 GetEnumerator() 以返回我自己的 Enumerator 类。该枚举器类将具有两个附加属性,这些属性将在使用枚举器时更新。

为了简单起见(这不是确切的业务案例),我们假设这些属性是 CurrentIndexRunningTotal

我可以在 foreach 循环中手动管理这些属性,但我宁愿封装此功能以供重用,并且枚举器似乎是正确的位置。

问题: foreach 隐藏了所有的枚举器业务,那么有没有办法在 foreach 语句中访问当前的枚举器,以便我可以检索我的属性?或者我是否必须使用 foreach,使用令人讨厌的旧 while 循环,并自己操作枚举器?

I have a List class, and I would like to override GetEnumerator() to return my own Enumerator class. This Enumerator class would have two additional properties that would be updated as the Enumerator is used.

For simplicity (this isn't the exact business case), let's say those properties were CurrentIndex and RunningTotal.

I could manage these properties within the foreach loop manually, but I would rather encapsulate this functionality for reuse, and the Enumerator seems to be the right spot.

The problem: foreach hides all the Enumerator business, so is there a way to, within a foreach statement, access the current Enumerator so I can retrieve my properties? Or would I have to foreach, use a nasty old while loop, and manipulate the Enumerator myself?

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

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

发布评论

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

评论(3

羁〃客ぐ 2024-08-20 16:50:39

严格来说,我想说,如果您想完全按照您所说的进行操作,那么是的,您需要调用 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 对象使您可以控制如何迭代器执行。我从这个问题中得到的印象是,您需要某种方法来访问迭代器的内部工作原理,以便您可以在迭代过程中操纵它的迭代方式,但如果情况并非如此,那么这个解决方案可能对于你所需要的东西来说太过分了。

Strictly speaking, I would say that if you want to do exactly what you're saying, then yes, you would need to call GetEnumerator and control the enumerator yourself with a while loop.

Without knowing too much about your business requirement, you might be able to take advantage of an iterator function, such as something like this:

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

Then you can do this:

        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!
        }

Updated:

Ok, so here's a follow-up with what I've termed my "fishing hook" solution. Now, let me add a disclaimer that I can't really think of a good reason to do something this way, but your situation may differ.

The idea is that you simply create a "fishing hook" object (reference type) that you pass to your iterator function. The iterator function manipulates your fishing hook object, and since you still have a reference to it in your code outside, you have visibility into what's going on:

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

You would utilize it like this:

        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

Essentially, the FishingHook object gives you some control over how the iterator executes. The impression I got from the question was that you needed some way to access the inner workings of the iterator so that you could manipulate how it iterates while you are in the middle of iterating, but if this is not the case, then this solution might be overkill for what you need.

舞袖。长 2024-08-20 16:50:39

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

With foreach you indeed can't get the enumerator - you could, however, have the enumerator return (yield) a tuple that includes that data; in fact, you could probably use LINQ to do it for you...

(I couldn't cleanly get the index using LINQ - can get the total and current value via Aggregate, though; so here's the tuple approach)

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);
        }
    }
}
溺ぐ爱和你が 2024-08-20 16:50:39

您还可以根据您的要求以更实用的方式执行类似的操作。您所要求的可以被认为是将多个序列“压缩”在一起,然后一次迭代它们。您给出的示例的三个序列是:

  1. “值”序列
  2. “索引”序列
  3. “运行总计”序列

下一步是分别指定这些序列中的每一个:

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

最后一个更有趣......我能想到的两种方法是使用一个临时变量来对序列求和,或者重新计算每个项目的总和。第二个显然性能较差,我宁愿使用临时的:

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 功能的源代码。这确实是一小段简单的代码。

You can also do something like this in a more Functional way, depending on your requirements. What you are asking can be though of as "zipping" together multiple sequences, and then iterating through them all at once. The three sequences for the example you gave would be:

  1. The "value" sequence
  2. The "index" sequence
  3. The "Running Total" Sequence

The next step would be to specify each of these sequences seperately:

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

The last one is more fun... the two methods I can think of are to either have a temporary variable used to sum up the sequence, or to recalculate the sum for each item. The second is obviously much less performant, I would rather use the temporary:

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

The last step would be to zip these all together. .Net 4 will have the Zip operator built in, in which case it will look like this:

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

This obviously gets noisier the more things you try to zip together.

In the last link, there is source for implementing the Zip function yourself. It really is a simple little bit of code.

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