4

Linq has a method Select, which takes a Func<TIn, TOut> and maps it over an iterable.

I'm trying to come up with a good name for a method that takes an Action <TIn>, transforms it to return its argument, and maps that over an iterable.

The method is

public static IEnumerable<T> MyMap<T>(this IEnumerable<T> source, Action<T> action)
{
    return source.Select(i => {action(i); return i;});
}

The initial use for this would be binding events on objects:

Controls.AddRange(Enumerable.Range(1, 10).Select(n => new Button(){Text = n.ToString()}).MyMap(b => b.Click += NumberButton_Click).ToArray());

In C#6, this is redundant as object initializers can bind events, but I'm stuck in 5 (where it's invalid to do so) for the forseeable future for corporate reasons.

I'm aware of List.ForEach, but that's a void method, I want something I can chain, as seen above.

Edit: Button example was just an example of how I would use it.

The code it would be used in is closer to

private void DoStuff(IEnumerable<Foo> foos)
{
    var fooList = foos.ToList();
    foreach (var foo in fooList)
    {
        Foo localFoo = foo;
        localFoo.ValueChanged += (sender, e) =>
            {
                var newArgs = new FooBarEventArgs(localFoo.Bar, e.Baz);
                this.OnFooBar(newArgs)
            };
    }

    _wrappedClass.DoStuff(fooList);
}

So I don't have control over creation of the IEnumerable, and there isn't a single element equivalent to DoStuff. However, with MyMap, it becomes (IMHO) cleaner:

private void DoStuff(IEnumerable<Foo> foos)
{
    _wrappedClass.DoStuff(foos.MyMap(f => f.ValueChanged += (sender, e) => this.OnFooBar(new FooBarEventArgs(f.Bar, e.Baz)));
}

closed as primarily opinion-based by gbjbaanb, Ixrec, Thomas Owens May 19 '16 at 15:03

Many good questions generate some degree of opinion based on expert experience, but answers to this question will tend to be almost entirely based on opinions, rather than facts, references, or specific expertise. If this question can be reworded to fit the rules in the help center, please edit the question.

  • How about Do(), Each() or Exec()? Since this is only going to be in your specific domain, you should choose whatever reads best for your audience == your current+future developers – Mattias Åslund May 6 '16 at 18:38
  • 8
    How about "SmellyTransform" 8-O? Honestly, when you start using Linq with side effects, this quickly becomes a code smell. Linq functions with modifying effects will at least violate the principle of least astonishment. – Doc Brown May 6 '16 at 18:45
  • ChainableForEach – zgnilec May 12 '16 at 16:04
11

Linq functions with modifying effects will at least violate the principle of least astonishment, so what you have in mind is a code smell, to my understanding.

For your use case, I think it is much more readable when you do not try to push as many command into one line as you can. Why not write a function like

IEnumerable<Button> CreateButtons()
{
     for(int n=0;i<10;++n)
     {
         var b = new Button();
         b.Text=n.ToString();
         b.Click += NumberButton_Click;
         yield return b;
     }
 }

However, if you still want to achieve this with one Linq expression, try

        Controls.AddRange(Enumerable.Range(1, 10).Select(
            n => {
                var b = new Button();
                b.Text = n.ToString();
                b.Click += NumberButton_Click;
                return b;
            }));

I would even consider to put the button creation into a named function like CreateButton(int n), this leaves you with

Controls.AddRange(Enumerable.Range(1, 10).Select( n => CreateButton(n)));

This way, you do not need to invent a smelly MyMap function.

To your 2nd example: IMHO the first variant without MyMap can be made more comprehensive by leaving out the unnecessary local variables. However, putting the event handler stuff into a Select expression works similar:

wrappedClass.DoStuff(foos.Select(f => 
   { 
       f.ValueChanged += 
             (sender, e) => this.OnFooBar(new FooBarEventArgs(f.Bar, e.Baz));
       return f;
   }));

Nevertheless this is a solution with side effects. For increased readability, why not give the operation which adds the handlers a name?

IEnumerable<Foo> AddHandlers( IEnumerable<Foo> foos)
{
     foreach (f in foos)
     {
        f.ValueChanged += 
             (sender, e) => this.OnFooBar(new FooBarEventArgs(f.Bar, e.Baz));
        yield return f;
     }
 }
  • See edit for better example of the context I'll be using it in. – RoadieRich May 6 '16 at 19:24
  • The last code example could just use the method group. – Stefan Hanke May 6 '16 at 20:00
  • @RoadieRich: as a side note, I recommend Eric Lippert's blog entry about ForEach and side effects: blogs.msdn.microsoft.com/ericlippert/2009/05/18/… So do yourself a favor and avoid creating Linq queries with side effects, your program's quality will benefit from this. – Doc Brown May 8 '16 at 15:33
3

The MoreLINQ library has this method and calls it Pipe, which I find very appropriate.

Pipe   |   Executes the given action on each element in the source sequence and yields it

If you're interested - you can see it's source code here.

Not the answer you're looking for? Browse other questions tagged or ask your own question.