1

Possible Duplicate:
How to get the index of an element in an IEnumerable?

i have this following function that accepts ienumerable string list.

I loop through all the strings and if its value equals "TestName" (case insensitive), i return its position.

    int GetMyTestColumnPosition(IEnumerable<string> TitleNames)
    {
        foreach (var test in TitleNames)
        {
            if (string.Compare(test, "testname", stringComparison.CurrentCultureIgnoreCase) == 0)
            {
                // return TitleNames.IndexOf(test); does not work!
            }
        }
    } 

EDIT: I changed the parameter to "IList<string>" and this works! But,

  1. How to find index or position of a string within an ienumerable string list ?
  2. Why does the ienumerable does not support index ? (we are not changing any value with in the list, we are just finding out its position!)
Community
  • 1
  • 1
  • http://stackoverflow.com/questions/1290603/how-to-get-the-index-of-an-element-in-an-ienumerable – Habib Jan 15 '13 at 10:45
  • 1
    See Jon Skeet's SmartEnumerable class, which works great for this: http://msmvps.com/blogs/jon_skeet/archive/2007/07/27/smart-enumerations.aspx – Matthew Watson Jan 15 '13 at 11:00
  • If you use `IList titleNames` you don't even need to write your own method, since `titleNames.IndexOf("testname")` gives your result in that case. If you want to use `IEnumerable`, LINQ will be helpful. Here's the full method: `int GetMyTestColumnPosition(IEnumerable titleNames) { return titleNames.Select(Tuple.Create).Where(t => string.Equals(t.Item1 , "testname", StringComparison.CurrentCultureIgnoreCase)).Select(t => t.Item2).DefaultIfEmpty(-1).First(); }` – Jeppe Stig Nielsen Jan 15 '13 at 11:26

5 Answers5

5

Well, since IEnumerables are used for enumerations, it's not such a surprise they don't have an IndexOf method. You can create an extension method if you want.

However, since you're already enumerating, what's the point of calculating the index again? Do something like this:

int index = 0;
foreach(var test in TitleNames)
{
    if(...) return index;
    index++;
}

Come to think of it, this is the extension method you want:

public static int IndexOf(this IEnumerable<T> list, T item)
{
    int index = 0;
    foreach(var l in list)
    {
        if(l.Equals(item))
            return index;
        index++;
    }
    return -1;
 }

Just remember to add checks for nulls, and maybe supply an optional comparer.

zmbq
  • 38,013
  • 14
  • 101
  • 171
  • -1 IEnumerable doesn't support indexes. You can't rely on enumerator implementation of the concrete type to return objects in order. – ken2k Jan 15 '13 at 10:52
  • 1
    In almost all IEnumerables you can. Obviously if an IEnumerable doesn't have any specific order, and enumerating twice yields different items each time, there's no point in IndexOf. Don't call it, then. – zmbq Jan 15 '13 at 10:54
  • 1
    You can, because of implementation details. If you want index support, then you use IList, not IEnumerable. – ken2k Jan 15 '13 at 10:55
  • If your collections is an IList, of course. I can think of plenty of examples where the random-accessness of an IList doesn't make sense, but the data is stable and doesn't move around. Anyway, theory aside, in reality I think this will almost never be a problem, and when it is, an extension method or lack of it is not going to save you... – zmbq Jan 15 '13 at 11:00
5

You can pass the index in the overloads to Select or Where:

var found = TitleNames
    .Select((str, index) => new { str, index })
    .Where(x => x.str.Equals("testname", StringComparison.CurrentCultureIgnoreCase))
    .FirstOrDefault();

if (found != null)
    return found.index;
return -1;
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • This will work, but it is a clear case where LINQ is not you friend. An ordinary loop (like OP had, but with tracking the index) will be a lot clearer to the reader, and probably faster, too. – zmbq Jan 15 '13 at 10:47
  • & I'd use `FirstOrDefault` and a null-check rather than `Any` and `First` and iterating twice. – Rawling Jan 15 '13 at 10:50
  • Thanks for the linq version @Tim – now he who must not be named. Jan 15 '13 at 10:52
  • @Rawling: `Any` is very clear and understandable and it doesn't iterate but peeks only the first. `FirstOrDefault` (or `First` with a custom `DefaultIfEmpty(-1)`) are more verbose. – Tim Schmelter Jan 15 '13 at 10:53
  • 2
    @Tim If the first match is the last item in the list, `Any` will iterate through the whole list to find that match, and then `First` will iterate through it again to find the first item. – Rawling Jan 15 '13 at 10:57
  • 2
    @Rawling: Touché! ;) (edited) – Tim Schmelter Jan 15 '13 at 11:01
3

From MSDN about IList

Represents a non-generic collection of objects that can be individually accessed by index.

IEnumerable is a simple enumerator and does not support indexes.

Jason Evans
  • 28,906
  • 14
  • 90
  • 154
0
var item = 
    TitleNames.Select( (tn, index) => new{tn, index})
      .FirstOrDefault(x => 
        string.Equals(x.tn, 
                      "testname", 
                      StringComparison.CurrentCultureIgnoreCase));
return item==null ? -1 : item.index;
spender
  • 117,338
  • 33
  • 229
  • 351
0

TitleNames is IEnumerable, so it doesn't support indexes.

You can't rely on enumeration:

int i = 0;
foreach(var test in TitleNames)
{
    i++;
}

to compute an index.

You could build your own class that inherits from IEnumerable<T> and that return objects on random order when enumerating.

If most concrete .Net types are generally index-friendly, it's pure implementation details.

So, to answer your question: you can't get index of object from IEnumerable<T>. Use IList<T> if you want index support.

ken2k
  • 48,145
  • 10
  • 116
  • 176