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

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

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

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

有帮助吗?

解决方案

严格地说,我会说,如果你想要做的正是你在说什么,那么,你就需要调用的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对象让您如何迭代执行一些控制。我从这个问题得到的印象是,你需要一些方法来访问迭代器的内部工作原理,让你可以操纵它如何迭代,而你在迭代的中间,但如果不是这样的话,那么这种解决方案可能是矫枉过正,你所需要的。

其他提示

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top