Richard Ev
Richard Ev

Reputation: 54127

foreach with generic List, detecting first iteration when using value type

When foreaching through a generic list I often want to do something different for the first element in the list:

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

This will output:

    First object - do something special
    object Do something else
    object Do something else
    object Do something else

This is all fine and dandy.

However if my generic list is of a value type, this approach will fail.

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

This will output:

    First int - do something special
    First int - do something special
    First int - do something special
    First int - do something special

Now I know I could recode this to add a boolean flag variable or traditional for loop, but I am wondering if there's any way to find out if a foreach loop is on the first iteration of its looping.

Upvotes: 19

Views: 32255

Answers (9)

Sylvain Rodrigue
Sylvain Rodrigue

Reputation: 4994

Select() can return the index of the current element. With a simple extension method, you can do this:

var ints = new List<int> { 1, 2, 3, 4 };

foreach (var item in ints.SelectWithIndex())
{
    if (item.index == 0)
        Console.WriteLine($"First: {item.val}");
    else
        Console.WriteLine($"{item.val}");
}

The extension method SelectWithIndex adds an index to each value of a list (it returns a list of tuples having to fields: val and index).

static class MyExtensions
{
    public static IEnumerable<(T val, int index)> SelectWithIndex<T>(this List<T> list)
    {
        return list.Select((i, j) => CreateValIndex(i, j));
    }

    public static (T val, int index) CreateValIndex<T>(T val, int index)
    {
        return (val, index);
    }
}

Upvotes: 0

T&#226;n
T&#226;n

Reputation: 1

Editing way from another answer:

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

Reference link about:

Using Skip: Enumerable.Skip Method (IEnumerable, Int32)

Using IList<T>: List or IList

Upvotes: 0

Marc Gravell
Marc Gravell

Reputation: 1063559

Well, you could code it using explicit iteration:

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
     }
  }
}

The bool flag option (with foreach) is probably easier though... that is what I (almost) always do!

Another option would be 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
}

The downside of the above is that it tries to look at the list 3 times... since we know it is a list, that is fine - but if all we had was an IEnumerable<T>, it would only be sensible to iterate it once (since the source might not be re-readable).

Upvotes: 29

Daniel Schaffer
Daniel Schaffer

Reputation: 57872

Since you're already using LINQ, you could do this:

var list = new List<Int32>();

// do something with list.First();

foreach (var item in list.Skip(1))
{
    // do other stuff
}

Upvotes: 3

Jon Skeet
Jon Skeet

Reputation: 1502406

A while ago I wrote SmartEnumerable (part of MiscUtil) which lets you know if the current element is the first or last, as well as its index. That may help you... it's part of MiscUtil, which is open source - you can take just SmartEnumerable under the same licence, of course.

Sample code (c'n'p from the web page):

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: Note that while it works with reference types with distinct references, it'll still fail if you give it a list where the first reference crops up elsewhere in the list.

Upvotes: 10

Steven Robbins
Steven Robbins

Reputation: 26599

If you really don't want to use a boolean, you could do something like:

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

But that seems a bit.. wierd :)

Upvotes: 0

GWLlosa
GWLlosa

Reputation: 24433

The issue you're having is because equality for value types is based on the actual value, not the reference itself. Specifically, in your example, your list is all zeroes, so its asking if currentItem == firstItem, and since they're both zero, this is always true. For this kind of thing, I've always wound up using a boolean flag.

Upvotes: 1

Jeremy
Jeremy

Reputation: 46410

Instead of if (i == ints.First()), does it work if you use if (i.Equals(ints.First())?

Upvotes: 0

Amy B
Amy B

Reputation: 110171

foreach(int i in ints.Take(1))
{
 //do first thing
}

foreach(int i in ints.Skip(1))
{
  //do other things
}

Upvotes: 7

Related Questions