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)));
}
3
  • 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 May 6, 2016 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, 2016 at 18:45
  • ChainableForEach
    – apocalypse
    May 12, 2016 at 16:04

2 Answers 2

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;
     }
 }
3
  • See edit for better example of the context I'll be using it in.
    – RoadieRich
    May 6, 2016 at 19:24
  • The last code example could just use the method group. May 6, 2016 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, 2016 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.