tags:

views:

575

answers:

2

Given a datasource like that:

var c = new Car[]
{
  new Car{ Color="Blue", Price=28000},
  new Car{ Color="Red", Price=54000},
  new Car{ Color="Pink", Price=9999},
  // ..
};

How can I find the index of the first car satisfying a certain condition with LINQ?

EDIT:

I could think of something like this but it looks horrible:

int firstItem = someItems.Select((item, index) => new    
{    
    ItemName = item.Color,    
    Position = index    
}).Where(i => i.ItemName == "purple")    
  .First()    
  .Position;

Will it be the best to solve this with a plain old loop?

+5  A: 
myCars.Select((v, i) => new {car = v, index = i}).First(myCondition).index;
Yuriy Faktorovich
+1  A: 

An IEnumerable is not an ordered set.
Although most IEnumerables are ordered, some (such as Dictionary or HashSet) are not.

Therefore, LINQ does not have an IndexOf method.

However, you can write one yourself:

///<summary>Finds the index of the first item matching an expression in an enumerable.</summary>
///<param name="items">The enumerable to search.</param>
///<param name="predicate">The expression to test the items against.</param>
///<returns>The index of the first matching item, or -1 if no items match.</returns>
public static int FindIndex<T>(this IEnumerable<T> items, Func<T, bool> predicate) {
    if (items == null) throw new ArgumentNullException("items");
    if (predicate == null) throw new ArgumentNullException("predicate");

    int retVal = 0;
    foreach (var item in items) {
        if (predicate(item)) return retVal;
        retVal++;
    }
    return -1;
}
///<summary>Finds the index of the first occurence of an item in an enumerable.</summary>
///<param name="items">The enumerable to search.</param>
///<param name="item">The item to find.</param>
///<returns>The index of the first matching item, or -1 if the item was not found.</returns>
public static int IndexOf<T>(this IEnumerable<T> items, T item) { return items.FindIndex(i => EqualityComparer<T>.Default.Equals(item, i)); }
SLaks
Although it does have an ElementAt method. Which takes an index as the argument.
Yuriy Faktorovich
Yes; I don't know why that method exists.
SLaks
C# has a Predicate delegate for you, how come you used Func<T, bool>?
Yuriy Faktorovich
Because that's what all of the other LINQ methods use. It makes the delegate signature clearer in the tooltip. `Predicate`, `Comparison`, and friends were effectively replaced by the `Func` delegates in .Net 3.5.
SLaks
Cool, I've learned something.
Yuriy Faktorovich
@SLaks, since this method already exists and is well known in the `List<T>` class, you should name it `FindIndex` and it should take a `Predicate<T>` instead of `Func<T, bool>`. The `IndexOf` takes a `T` object instead of a selector. Remember, consistency is the #1 strong point of the CLI: http://msdn.microsoft.com/en-us/library/x1xzf2ca.aspx
280Z28
@280Z28: LINQ is already inconsist with `List<T>` - `FindAll(Predicate<T>)` vs. `Where(Func<T, bool>)`, `Exists(Predicate<T>)` vs. `Any(Func<T, bool>)`, `ConvertAll(Converter<T, TOutput>)` vs. `Select(Func<T1, T2>)`, etc.
SLaks
@SLaks: That may be true, but 1) in each case, the new name does not alias a name present in a similar context earlier versions, 2) for FindAll/Where and ConvertAll/Select, the new version has different semantics, and the old name doesn't make sense in a lazy-evaluation context, and 3) for Exists/Any, the old name is misleading. You use this as a rationale for your answer, but you didn't follow #1, #2 certainly doesn't apply, and #3 is debatable at best.
280Z28