Подтвердить что ты не робот

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

4b9b3361

Ответ 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