Search code examples
c#linqselectienumerableclass-extensions

Extension of IEnumerable's Select to include the source in the selector


One of IEnumerable's overload is:

public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, TResult> selector);

In the selector I wish to include the source. I know this sounds counter-intuitive because you provide the source to Select in the first place, but JavaScript has something similar. I would want to use it in a quick situation like this one here:

var greetings = new List<string> { "John", "Keith", "Sarah", "Matt" }.Select((name, index, source) => {
    if (name == source.First())
        return $"{name} (Todays Winner)";
    return name;
});

The above will have an error because Select's selector parameter does not return 3 values. Just the current object, and index. I want it to include the source.

I don't want to first create the list separately and then do .first on it.

Here is how far I've gone with the extension; I'm not sure how to implement it.

public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, TResult, IEnumerable<TSource>> selector)
{
    //not sure what to put in here, must be missing something simple ;(
}

Update

The above situation is just a made up example. My actual case requires using .Last() not .First() so index won't be useful since we don't know what the last index will be, as opposed to zero being first. Hence my need for the source to be passed back.


Solution

  • This should do it:

    public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int, IEnumerable<TSource>, TResult> selector)
    {
        using (var enumerator = source.GetEnumerator()) {
            for (var i = 0 ; enumerator.MoveNext() ; i++) {
                yield return selector(enumerator.Current, i, source);
            }
        }
    }
    

    Note that you have written the wrong type for the selector parameter. It should be Func<TSource, int, IEnumerable<TSource>, TResult>, not Func<TSource, int, TResult, IEnumerable<TSource>>.

    If you just want to check if an element is the first, why not just check index == 0?

    var greetings = new List<string> { "John", "Keith", "Sarah", "Matt" }.Select((name, index, source) => {
        if (index == 0)
            return $"{name} (Todays Winner)";
        return name;
    });