foreach с универсальным List, обнаружение первой итерации при использовании типа значения - PullRequest
18 голосов
/ 07 января 2009

Когда 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 на первой итерации его цикла.

Ответы [ 8 ]

28 голосов
/ 07 января 2009

Ну, вы могли бы закодировать его, используя явную итерацию:

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>, это было бы разумно повторить его один раз (поскольку источник может быть не читабельным).

10 голосов
/ 07 января 2009

Некоторое время назад я написал 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" : "");
        }
    }
}

РЕДАКТИРОВАТЬ: обратите внимание, что, хотя он работает с ссылочными типами с различными ссылками, он все равно потерпит неудачу, если вы дадите ему список, в котором первая ссылка появится в другом месте списка.

7 голосов
/ 07 января 2009
foreach(int i in ints.Take(1))
{
 //do first thing
}

foreach(int i in ints.Skip(1))
{
  //do other things
}
2 голосов
/ 07 января 2009

Поскольку вы уже используете LINQ, вы можете сделать это:

var list = new List<Int32>();

// do something with list.First();

foreach (var item in list.Skip(1))
{
    // do other stuff
}
1 голос
/ 07 января 2009

Проблема в том, что равенство для типов значений основано на фактическом значении, а не на самой ссылке. В частности, в вашем примере ваш список - все нули, поэтому он спрашивает, является ли currentItem == firstItem, и так как они оба равны нулю, это всегда верно Для такого рода вещей я всегда использовал логический флаг.

0 голосов
/ 25 ноября 2016

Редактирование пути из другого ответа:

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 (IEnumerable, Int32)

Использование IList<T>: Список или IList

0 голосов
/ 07 января 2009

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

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");
    }
}

Но это кажется немного .. странным:)

0 голосов
/ 07 января 2009

Вместо if (i == ints.First()) работает ли, если вы используете if (i.Equals(ints.First())?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...