dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.41k stars 4.76k forks source link

[API Proposal]: IndexOf extension method on IEnumerable<T> #110045

Open rjperes opened 16 hours ago

rjperes commented 16 hours ago

Background and motivation

As the counterpart to ElementAt(), and Index(), I suggest the creation of an IndexOf() method, which would return the index of the passed element on a list, or -1 if the element is not found. This could even be implemented in LINQ to databases, as there are ways to obtain the index of a row.

API Proposal

namespace System.Collections.Generic;

public static class EnumerableExtensions
{
    public static int IndexOf<T>(this IEnumerable<T> source, T element)
    {
        ArgumentNullException.ThrowIfNull(source);
        return source.Index().Where(elm => elm.Item.Equals(element)).Select(x => x.Index).FirstOrDefault(-1);
    }
}

API Usage

string[] colours = ["red", "green", "blue"];
var greenIndex = colours.IndexOf("green");

Alternative Designs

No response

Risks

No breaking changes, as it's a new method.

dotnet-policy-service[bot] commented 16 hours ago

Tagging subscribers to this area: @dotnet/area-system-linq See info in area-owners.md if you want to be subscribed.

stephentoub commented 16 hours ago

string[] colours = ["red", "green", "blue"]; var greenIndex = colours.IndexOf("green");

That already works today if you set <LangVersion>preview</LangVersion> in your csproj, as MemoryExtensions.IndexOf then automatically applies to arrays. Plus IList<T> already has an IndexOf. So this would really only be about non-indexible collections. I'd be concerned about exposing something like this for non-indexible enumerables, if for no other reason than there's no guarantee the index will be correct for a second enumeration.

rjperes commented 16 hours ago

Right, and how is that different from ElementAt, for non-indexable collections, which already exists? As for the method you mentioned, it will work for strings, but not in general for any T.

stephentoub commented 16 hours ago

As for the method you mentioned, it will work for strings, but not in general for any T.

It'll work for any T : IEquatable<T>, and https://github.com/dotnet/runtime/issues/28934 will work for all other T.

and how is that different from ElementAt, for non-indexable collections, which already exists?

ElementAt is problematic, for a variety of reasons, including that the data could change on subsequent enumeration and including that it can easily lead to unexpected O(N^2) evaluation. I don't think we should be doubling-down on it.

huoyaoyuan commented 15 hours ago

and how is that different from ElementAt, for non-indexable collections, which already exists?

ElementAt is problematic, for a variety of reasons, including that the data could change on subsequent enumeration and including that it can easily lead to unexpected O(N^2) evaluation. I don't think we should be doubling-down on it.

ElementAt can be effectively considered as Skip(n).Take(1), which is a one-shot usage of the enumerable in case you do need only one element.

For IndexOf, the index is much less usable without consuming the enumerable again.

Plus IList<T> already has an IndexOf. So this would really only be about non-indexible collections.

How about IReadOnlyList<T>? It doesn't provide lookup because of covariance.

eiriktsarpalis commented 14 hours ago

Agree that we shouldn't be exposing this due to the index not being practical to use with arbitrary enumerables.

julealgon commented 11 hours ago

Agree that we shouldn't be exposing this due to the index not being practical to use with arbitrary enumerables.

@eiriktsarpalis what if you just want to know the position at that point in time for some other purpose, and you don't intend to try to use it to index back into the enumerable?

eiriktsarpalis commented 7 hours ago

Can you share an example where this might be the case? Is it common enough to warrant inclusion in System.Linq and not be a helper when necessary?