IEnumerable foreach, сделать что-то другое для последнего элемента
У меня есть IEnumerable<T>
. Я хочу сделать одну вещь для каждого элемента коллекции, кроме последнего элемента, к которому я хочу сделать что-то еще. Как я могу это сделать аккуратно? В псевдокоде
foreach (var item in collection)
{
if ( final )
{
g(item)
}
else
{
f(item)
}
}
Итак, если мой IEnumerable был Enumerable.Range(1,4)
, я бы сделал f (1) f (2) f (3) g (4). NB. Если мой IEnumerable имеет длину 1, я хочу g (1).
Мой IEnumerable бывает вроде дрянной, делая Count()
столь же дорогим, как и цикл над всем.
Ответы
Ответ 1
Поскольку вы упоминаете IEnumerable[<T>]
(not IList[<T>]
и т.д.), мы не можем полагаться на counts и т.д., поэтому у меня возникнет соблазн развернуть foreach
:
using(var iter = source.GetEnumerator()) {
if(iter.MoveNext()) {
T last = iter.Current;
while(iter.MoveNext()) {
// here, "last" is a non-final value; do something with "last"
last = iter.Current;
}
// here, "last" is the FINAL one; do something else with "last"
}
}
Обратите внимание, что это технически справедливо только для IEnuemerable<T>
; для не-generic, вам понадобится:
var iter = source.GetEnumerator();
using(iter as IDisposable) {
if(iter.MoveNext()) {
SomeType last = (SomeType) iter.Current;
while(iter.MoveNext()) {
// here, "last" is a non-final value; do something with "last"
last = (SomeType) iter.Current;
}
// here, "last" is the FINAL one; do something else with "last"
}
}
Ответ 2
Как и в ответе Marc, но вы можете написать метод расширения для его завершения.
public static class LastEnumerator
{
public static IEnumerable<MetaEnumerableItem<T>> GetLastEnumerable<T>(this IEnumerable<T> blah)
{
bool isFirst = true;
using (var enumerator = blah.GetEnumerator())
{
if (enumerator.MoveNext())
{
bool isLast;
do
{
var current = enumerator.Current;
isLast = !enumerator.MoveNext();
yield return new MetaEnumerableItem<T>
{
Value = current,
IsLast = isLast,
IsFirst = isFirst
};
isFirst = false;
} while (!isLast);
}
}
}
}
public class MetaEnumerableItem<T>
{
public T Value { get; set; }
public bool IsLast { get; set; }
public bool IsFirst { get; set; }
}
Затем назовите его так:
foreach (var row in records.GetLastEnumerable())
{
output(row.Value);
if(row.IsLast)
{
outputLastStuff(row.Value);
}
}
Ответ 3
Если вы хотите сделать это максимально эффективно, нет другого выбора, кроме как эффективно смотреть не только на текущий, но и на "следующий" или "предыдущий" элемент, чтобы вы могли отложить решение о том, что делать после вас иметь эту информацию. Например, если предположить, что T
- тип элементов коллекции:
if (collection.Any()) {
var seenFirst = false;
T prev = default(T);
foreach (var current in collection) {
if (seenFirst) Foo(prev);
seenFirst = true;
prev = current;
}
Bar(prev);
}
Посмотрите на действие.
Ответ 4
Я бы не рекомендовал его, но я думаю, вы могли бы сделать что-то вроде этого...
object m_item = notPartOfListFlag = new object();
foreach(var item in enumerator){
if(m_item != notPartOfListFlag)
{
//do stuff to m_item;
}
m_item = item;
}
//do stuff to last item aka m_item;
Но я бы попытался использовать какую-то коллекцию, которая раскрывает позицию элементов в списке, а затем используйте
if(collection.IndexOf(item) == collection.Count-1) do stuff
Ответ 5
не на 100% уверен, что мне это нравится, но вы всегда можете отложить использование элемента до тех пор, пока не переместитесь на один шаг в массив IEnumerable, таким образом, когда вы доберетесь до конца, вы не использовали последнюю запись.
он избегает необходимости принудительно подсчитывать счетчик.
object item = null;
foreach (var a in items)
{
// if item is set then we can use it.
if (item != null)
{
// not final item
f(item);
}
item = a;
}
// final item.
g(item);