如何通过 IEnumerable 访问 foreach 迭代中的下一个和上一个项目?
How to access the next and previous items within a foreach iteration over an IEnumerable?
我想在处理当前项目并迭代 IEnumerable<object>
时访问下一个和上一个项目(如果在范围内),它缺少索引器(据我所知)。
我不想要求 List<object>
集合或数组来实现此目的,尽管这是我目前所知道的唯一选择。
我也希望尽可能避免使用 Linq 表达式。
目前我的方法是这样定义的:
public static void PrintTokens(IEnumerable<object> tokens)
{
foreach (var token in tokens)
{
// var next = ?
// var prev = ?
}
}
我认为原始 IEnumerable
不提供此功能,但 LinkedList 提供此功能。
您正常执行循环,但您当前所在的项目变为 next
,前一个项目为 current
,而前一个项目为 prev
。
object prev = null;
object current = null;
bool first = true;
foreach (var next in tokens)
{
if (!first)
{
Console.WriteLine("Processing {0}. Prev = {1}. Next = {2}", current, prev, next);
}
prev = current;
current = next;
first = false;
}
// Process the final item (if there is one)
if (!first)
{
Console.WriteLine("Processing {0}. Prev = {1}. Next = {2}", current, prev, null);
}
如果您使用的是 C# 7.0+,您可以编写
(prev, current, first) = (current, next, false);
而不是循环结束时的三个独立语句。
来自文档:
IEnumerable exposes an enumerator, which supports a simple iteration.
因此,要实现您想要的效果,您可以采用以下四种方式之一:
- 在 tokens 变量上调用
ToList()
- 这样您就可以按索引访问项目
- 在 IEnumerable 上使用
ElementAt()
方法,但它会缺乏性能,因为每次调用此方法时都会枚举标记
- 按照您的建议,将
IEnumerable<object> tokens
更改为 IList<object> tokens
- 编写自定义循环以保存上一个、当前和下一个项目(下一个实际上是循环中的当前项目)
我写了一个扩展方法,其中 returns 一个包含 3 个对象的元组:上一个、当前和下一个:
public static class Extensions
{
public static IEnumerable<Tuple<T, T, T>> GetItems<T>(this IEnumerable<T> source)
where T : class
{
if (source != null)
{
// skip the first iteration to be able to include next item
bool skip = true;
T previous = default(T), current = default(T), next = default(T);
foreach (T item in source)
{
next = item;
if (!skip)
{
yield return new Tuple<T, T, T>(previous, current, next);
}
previous = current;
current = item;
skip = false;
}
if (!skip)
{
next = default(T);
yield return new Tuple<T, T, T>(previous, current, next);
}
}
}
}
希望对您有所帮助。也许使用自定义 class 而不是元组进行扩展。
你可以试试这个扩展方法。它是获取 "size" 上一项和 "size" 下一项以及当前项:
public static System.Collections.Generic.IEnumerable<TResult> Select<TSource, TResult>(this System.Collections.Generic.IEnumerable<TSource> source, int size, System.Func<Queue<TSource>, TSource, Queue<TSource>, TResult> selector)
{
if (source == null)
{
throw new System.ArgumentNullException("source");
}
if (selector == null)
{
throw new System.ArgumentNullException("selector");
}
Queue<TSource> prevQueue = new Queue<TSource>(size);
Queue<TSource> nextQueue = new Queue<TSource>(size);
int i = 0;
foreach (TSource item in source)
{
nextQueue.Enqueue(item);
if (i++ >= size)
{
TSource it = nextQueue.Dequeue();
yield return selector(prevQueue, it, nextQueue);
prevQueue.Enqueue(it);
if (prevQueue.Count > size)
{
prevQueue.Dequeue();
}
}
}
while (nextQueue.Any())
{
TSource it = nextQueue.Dequeue();
yield return selector(prevQueue, it, nextQueue);
prevQueue.Enqueue(it);
if (prevQueue.Count > size)
{
prevQueue.Dequeue();
}
}
}
我想在处理当前项目并迭代 IEnumerable<object>
时访问下一个和上一个项目(如果在范围内),它缺少索引器(据我所知)。
我不想要求 List<object>
集合或数组来实现此目的,尽管这是我目前所知道的唯一选择。
我也希望尽可能避免使用 Linq 表达式。
目前我的方法是这样定义的:
public static void PrintTokens(IEnumerable<object> tokens)
{
foreach (var token in tokens)
{
// var next = ?
// var prev = ?
}
}
我认为原始 IEnumerable
不提供此功能,但 LinkedList 提供此功能。
您正常执行循环,但您当前所在的项目变为 next
,前一个项目为 current
,而前一个项目为 prev
。
object prev = null;
object current = null;
bool first = true;
foreach (var next in tokens)
{
if (!first)
{
Console.WriteLine("Processing {0}. Prev = {1}. Next = {2}", current, prev, next);
}
prev = current;
current = next;
first = false;
}
// Process the final item (if there is one)
if (!first)
{
Console.WriteLine("Processing {0}. Prev = {1}. Next = {2}", current, prev, null);
}
如果您使用的是 C# 7.0+,您可以编写
(prev, current, first) = (current, next, false);
而不是循环结束时的三个独立语句。
来自文档:
IEnumerable exposes an enumerator, which supports a simple iteration.
因此,要实现您想要的效果,您可以采用以下四种方式之一:
- 在 tokens 变量上调用
ToList()
- 这样您就可以按索引访问项目 - 在 IEnumerable 上使用
ElementAt()
方法,但它会缺乏性能,因为每次调用此方法时都会枚举标记 - 按照您的建议,将
IEnumerable<object> tokens
更改为IList<object> tokens
- 编写自定义循环以保存上一个、当前和下一个项目(下一个实际上是循环中的当前项目)
我写了一个扩展方法,其中 returns 一个包含 3 个对象的元组:上一个、当前和下一个:
public static class Extensions
{
public static IEnumerable<Tuple<T, T, T>> GetItems<T>(this IEnumerable<T> source)
where T : class
{
if (source != null)
{
// skip the first iteration to be able to include next item
bool skip = true;
T previous = default(T), current = default(T), next = default(T);
foreach (T item in source)
{
next = item;
if (!skip)
{
yield return new Tuple<T, T, T>(previous, current, next);
}
previous = current;
current = item;
skip = false;
}
if (!skip)
{
next = default(T);
yield return new Tuple<T, T, T>(previous, current, next);
}
}
}
}
希望对您有所帮助。也许使用自定义 class 而不是元组进行扩展。
你可以试试这个扩展方法。它是获取 "size" 上一项和 "size" 下一项以及当前项:
public static System.Collections.Generic.IEnumerable<TResult> Select<TSource, TResult>(this System.Collections.Generic.IEnumerable<TSource> source, int size, System.Func<Queue<TSource>, TSource, Queue<TSource>, TResult> selector)
{
if (source == null)
{
throw new System.ArgumentNullException("source");
}
if (selector == null)
{
throw new System.ArgumentNullException("selector");
}
Queue<TSource> prevQueue = new Queue<TSource>(size);
Queue<TSource> nextQueue = new Queue<TSource>(size);
int i = 0;
foreach (TSource item in source)
{
nextQueue.Enqueue(item);
if (i++ >= size)
{
TSource it = nextQueue.Dequeue();
yield return selector(prevQueue, it, nextQueue);
prevQueue.Enqueue(it);
if (prevQueue.Count > size)
{
prevQueue.Dequeue();
}
}
}
while (nextQueue.Any())
{
TSource it = nextQueue.Dequeue();
yield return selector(prevQueue, it, nextQueue);
prevQueue.Enqueue(it);
if (prevQueue.Count > size)
{
prevQueue.Dequeue();
}
}
}