Foreach с общим списком, определение первой итерации при использовании типа значения
Когда foreach
через общий список я часто хочу сделать что-то другое для первого элемента в списке:
List<object> objs = new List<object>
{
new Object(),
new Object(),
new Object(),
new Object()
};
foreach (object o in objs)
{
if (o == objs.First())
{
System.Diagnostics.Debug.WriteLine("First object - do something special");
}
else
{
System.Diagnostics.Debug.WriteLine("object Do something else");
}
}
Это выведет:
First object - do something special
object Do something else
object Do something else
object Do something else
Это все прекрасно и денди.
Однако, если мой общий список имеет тип значения, этот подход не будет выполнен.
List<int> ints = new List<int> { 0, 0, 0, 0 };
foreach (int i in ints)
{
if (i == ints.First())
{
System.Diagnostics.Debug.WriteLine("First int - do something special");
}
else
{
System.Diagnostics.Debug.WriteLine("int Do something else");
}
}
Это выведет:
First int - do something special
First int - do something special
First int - do something special
First int - do something special
Теперь я знаю, что могу перекодировать это, чтобы добавить переменную флага boolean
или традиционный цикл for
, но мне интересно, есть ли способ узнать, является ли цикл foreach на первой итерации цикла.
Ответы
Ответ 1
Ну, вы можете закодировать его, используя явную итерацию:
using(var iter = ints.GetEnumerator()) {
if(iter.MoveNext()) {
// do "first" with iter.Current
while(iter.MoveNext()) {
// do something with the rest of the data with iter.Current
}
}
}
Опция bool flag (с foreach
), вероятно, проще, хотя... это то, что я (почти) всегда делает!
Другим вариантом будет LINQ:
if(ints.Any()) {
var first = ints.First();
// do something with first
}
foreach(var item in ints.Skip(1)) {
// do something with the rest of them
}
Недостатком вышеизложенного является то, что он пытается просмотреть список 3 раза... так как мы знаем, что это список, это нормально - но если бы все, что у нас было, было IEnumerable<T>
, это было бы разумно для его повторения один раз (поскольку источник не может быть перечитаемым).
Ответ 2
Некоторое время назад я написал SmartEnumerable (часть MiscUtil), который позволяет узнать, является ли текущий элемент первым или последним, так как а также его индекс. Это может помочь вам... это часть MiscUtil, которая является открытым исходным кодом - вы можете взять только SmartEnumerable под той же лицензией, конечно.
Пример кода (c'n'p с веб-страницы):
using System;
using System.Collections.Generic;
using MiscUtil.Collections;
class Example
{
static void Main(string[] args)
{
List<string> list = new List<string>();
list.Add("a");
list.Add("b");
list.Add("c");
list.Add("d");
list.Add("e");
foreach (SmartEnumerable<string>.Entry entry in
new SmartEnumerable<string>(list))
{
Console.WriteLine ("{0,-7} {1} ({2}) {3}",
entry.IsLast ? "Last ->" : "",
entry.Value,
entry.Index,
entry.IsFirst ? "<- First" : "");
}
}
}
EDIT: обратите внимание, что, хотя он работает со ссылочными типами с разными ссылками, он все равно будет терпеть неудачу, если вы дадите ему список, в котором первая ссылка появляется в другом месте в списке.
Ответ 3
foreach(int i in ints.Take(1))
{
//do first thing
}
foreach(int i in ints.Skip(1))
{
//do other things
}
Ответ 4
Поскольку вы уже используете LINQ, вы можете сделать это:
var list = new List<Int32>();
// do something with list.First();
foreach (var item in list.Skip(1))
{
// do other stuff
}
Ответ 5
Проблема, с которой вы сталкиваетесь, заключается в том, что равенство для типов значений основано на фактическом значении, а не на самой ссылке. В частности, в вашем примере ваш список равен нулю, поэтому его запрос: currentItem == firstItem, а так как они оба равны нулю, это всегда верно. Для такого рода вещей я всегда возвращался с использованием логического флага.
Ответ 6
Вместо if (i == ints.First())
работает ли это, если вы используете if (i.Equals(ints.First())
?
Ответ 7
Если вы действительно не хотите использовать логическое значение, вы можете сделать что-то вроде:
List<int> ints = new List<int> { 0, 0, 0, 0 };
System.Diagnostics.Debug.WriteLine("First int - do something special" + ints.FirstOrDefault().ToString());
foreach (int i in ints.Skip(1))
{
{
System.Diagnostics.Debug.WriteLine("int Do something else");
}
}
Но это кажется немного.. wierd:)
Ответ 8
Редактирование пути из другого ответа:
IList<object> objs = new List<object>
{
new Object(),
new Object(),
new Object(),
new Object()
};
// to access first element: objs[0]
// to access last element: objs[objs.Count - 1]
System.Diagnostics.Debug.WriteLine("First object - do something special");
foreach (object o in objs.Skip(1))
{
System.Diagnostics.Debug.WriteLine("object Do something else");
}
Ссылка на ссылку:
Использование Skip
: Enumerable.Skip Method (IEnumerable, Int32)
Использование IList<T>
: Список или IList