Как получить доступ к следующим и предыдущим элементам внутри итерации foreach через IEnumerable? - PullRequest
0 голосов
/ 02 апреля 2019

Я хочу получить доступ к следующим и предыдущим элементам (если в пределах диапазона) во время обработки текущего элемента и итерации по IEnumerable<object>, в котором отсутствует индексатор (насколько я знаю).

Я бы не сталЯ не хочу требовать коллекции List<object> или массива для этого, хотя это единственный вариант, который я знаю прямо сейчас.

Я бы также предпочел по возможности избегать выражений Linq.

Вот как определяется мой метод на данный момент:

public static void PrintTokens(IEnumerable<object> tokens)
{
    foreach (var token in tokens)
    {
        // var next = ?
        // var prev = ?
    }
}

Ответы [ 5 ]

4 голосов
/ 02 апреля 2019

Вы делаете свой цикл как обычно, но элемент, на котором вы сейчас находитесь, становится 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);

вместо трех отдельных операторов в конце цикла.

1 голос
/ 02 апреля 2019

Я написал метод расширения, который возвращает кортеж с 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);
            }
        }
    }
}

Надеюсь, это поможет. Возможно расширить с помощью собственного класса вместо кортежа.

0 голосов
/ 18 июля 2019

U можете попробовать этот метод расширения. Это получить «размер» предыдущего элемента и «размер» следующих элементов и текущего элемента:

    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();
            }
        }
    }
0 голосов
/ 02 апреля 2019

Из документации:

IEnumerable предоставляет перечислитель, который поддерживает простую итерацию .

Итак, чтобы достичь того, чего вы хотите, вы можете следовать одним из четырех способов:

  • Invoking ToList() для переменной tokens - таким образом вы можете получить доступ к элементам по индексу
  • Используйте метод ElementAt() в IEnumerable, но он не будет работать, так как токены будут перечисляться при каждом вызове этого метода
  • Как вы предложили, измените IEnumerable<object> tokens на IList<object> tokens
  • Создать пользовательский цикл, который сохраняет предыдущие, текущие и следующие элементы (следующим будет текущий элемент в вашем цикле)
0 голосов
/ 02 апреля 2019

Я думаю, что IEnumerable не предоставляет этого, но LinkedList предлагает.

...