题
我有一个 List 类,我想覆盖 GetEnumerator()
返回我自己的枚举器类。该枚举器类将具有两个附加属性,这些属性将在使用枚举器时更新。
为了简单起见(这不是确切的业务案例),我们假设这些属性是 CurrentIndex
和 RunningTotal
.
我可以在 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);
}
}
}
您还可以根据您的要求以更实用的方式执行类似的操作。您所要求的可以被视为将多个序列“压缩”在一起,然后一次迭代它们。您给出的示例的三个序列是:
- “值”序列
- “索引”序列
- “运行总计”序列
下一步是分别指定每个序列:
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 功能的源代码。这确实是一小段简单的代码。