3
votes

Goal: ForEach extension masking Select.

Reason: Select allows for method chaining while foreach does not; ForEach is more readable than Select.

The problem I'm running into is that I'm getting this error.

The type arguments for method 'System.Linq.Enumerable.Select(System.Collections.Generic.IEnumerable, System.Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly. (CS0411)

public static void ForEach<T>(this IEnumerable<T> elements, Action<T, int> action)
{
    elements.Select((elem, i) => action(elem, i));
}

I've tried doing elements.Select((T elem, int i) => action(elem, i)); but that produces the same error.

I've also attempted using Func instead of Action but there doesn't seem to be any way to use void for the return (which is what Action is for anyways). If I define the method to take a Func instead of Action then when I try to call it using Action I get the same error.

public static IEnumerable<TOut> ForEach<TIn, TOut>(this IEnumerable<TIn> elements, 
    Func<TIn, int, TOut> function)
{
    return elements.Select((elem, i) => function(elem, i))
        .ToList();
}

I don't understand which arguments cannot be inferred. There are ways I could get around this (using a for loop or foreach loop and track the index), but I would still like to figure out why this won't work.

Edit: I also just realized my other ForEach for Action (no index) also gets this error when I switch from a foreach loop to calling Select.

public static void ForEach<T>(this IEnumerable<T> elements, Action<T> action)
{
    elements.ToList().Select((T elem) => action(elem));
    /*foreach (var elem in elements)
        action(elem);*/
}
1
Why use Select at all? If you want to write ForEach, just use a foreach statement...Jon Skeet
Note that if your first method compiled, it wouldn't actually do anything, due to lazy evaluation.Jon Skeet
Well do you not understand any of the errors? Do you understand that Select is meant to take a projection, to go from one type to another? What do you expect the compiler to infer here, given that action(elem) and action(elem, i) don't return anything?Jon Skeet
If you're only interested in figuring out why it doesn't work, that should be the stated goal - because the best answer for the goal at the top of the question is "use a foreach loop" (inside your ForEach implementation).Jon Skeet
@BlakeThingstad What would a IEnumerable<void> look like?Mark Peters

1 Answers

7
votes

The problem is that Select expects a return value (you're projecting an object into something else) - of which your Action is returning void. void can't be converted into T, which is what Select is expecting.

The simple fix is to ensure something is returned at the end of your select function:

public static void ForEach<T>(this IEnumerable<T> elements, Action<T, int> action)
{
    elements.Select((elem, i) =>
    {
        action(elem, i);
        return elem;
    }).Count();
}

The action is called, and the collection continues to be enumerated. We need to call Count because nothing will actually happen until we attempt to get a value from each enumeration (thank you Jon Skeet!)

Note that this isn't how Select should be used - the OP specifically asked why/how to make it work with it.

To do this properly, as mentioned in the comments, the solution is to use an actual foreach loop with a counter:

public static void ForEach<T>(this IEnumerable<T> elements, Action<T, int> action)
{
    int count = 0;
    foreach (var item in elements)
    {
        action(item, count++);
    }
}

If you want to allow method chaining, you can do it this way:

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> elements, Action<T, int> action) 
{ 
    int count = 0; 
    foreach (var item in elements) 
    {
        action(item, count++); 
        yield return item; 
    }
}