8

I have a LINQ query that looks like this:

public IEnumerable<Foo> SelectFooBars()
{
    return
        from
            f in foos
        join
            b in bars
            on f.BarId equals b.Id
        select
            AddMissingProp(f, b.MissingProp);
}

public void AddMissingProp(Foo foo, string missingProp) // substitute this with inline lambda
{
    foo.MissingProp = missingProp;
    return foo;
}

I would like to get rid of AddMissingProp and use some form of a lambda in my select clause instead.

I attempted...

...
select
    (f, b) => { f.MissingProp = b.MissingProp; return f }

...but I got the following error:

A local variable named 'f' cannot be declared in this scope because it would give a different meaning to 'f', which is already used in a 'parent or current' scope to denote something else.

How can I "lambda-ize" my query?


Update

This also doesn't work:

...
select
    () => { f.MissingProp = b.MissingProp; return f }

I get the following error:

The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'.

I didn't change the join clause at all, so I'm perplexed.

devuxer
  • 41,681
  • 47
  • 180
  • 292
  • 1
    I don't know the LINQ syntax well enough to write it out, but wouldn't it be better do the mutating in a foreach(yourLinqQuery) statement? Having a select mutate the objects seems confusing. –  Jan 07 '10 at 20:20
  • @Doc Brown, that was actually just a typo in my example (my real code says `equals`). Fixed now. – devuxer Jan 07 '10 at 20:56

5 Answers5

5

I think icambron is right, IMHO the better readable version is this:

  var fooBar = from 
                 f in foos
               join 
                 b in bars
                 on f.BarId equals b.Id 
               select new {f,b};

   foreach(var x in fooBar)
        x.f.MissingProp = x.b.MissingProp;

   // EDIT due to comments: add this if you 
   // need IEnumerable<Foo> returned
   return fooBar.Select(fb => fb.f);

The from join select statements are for queries, they should not be misused for mutating the contents of a sequence.

EDIT: Here is another link providing some insights why using a functional form of ForEach is not a good idea.

Doc Brown
  • 19,739
  • 7
  • 52
  • 88
  • Thanks +1. I had thought about using `foreach` but I guess I was trying to be too fancy :) This is a good solution. – devuxer Jan 07 '10 at 20:58
  • Oops, one problem. This leaves me with an `IEnumerable` but I need an `IEnumerable`. I think if you add `return fooBar.Select(fb => fb.f);` to your answer, it will be correct. – devuxer Jan 07 '10 at 21:09
  • 2
    You should also be able to do `var foobar = (....).ForEach(....); ` and one line it! – Chris Marisic Jan 07 '10 at 21:24
  • @DanThMan: edited it, but you should change the return type of AddMissingProp to make the original question fit to the edited answe :-) – Doc Brown Jan 07 '10 at 21:38
  • @Chris: ForEach works on Array<>, not on IEnumerable<>. And I don't think this will make it better readable. – Doc Brown Jan 07 '10 at 21:43
3

You can give types to your parameters in a lambda expression but you need to use different names since you're already using f and b in the query.

(Foo f1, Bar b1) => ...

Edit

return
(
    from 
        f in foos 
    join
        b in bars 
        on f.BarId equals b.Id 
    select 
        new {f, b}
).select(foobar => {foobar.f.BarId = foobar.b.Id; return foobar.f});
devuxer
  • 41,681
  • 47
  • 180
  • 292
Rodrick Chapman
  • 5,437
  • 2
  • 31
  • 32
  • This doesn't work. It doesn't know that it should be passing in `f` and `b`. I also get the same error as if I use `() => { ... }` (see my update). – devuxer Jan 07 '10 at 20:28
  • Second times the charm :) Well, except the second `from` should be a `join`. But, otherwise, this is my favorite solution so far. – devuxer Jan 07 '10 at 21:28
  • I noticed another minor problem...it should be `{foobar.f.BarId = foobar.b.Id; return foobar.f};`. Alternatively, you could explicitly define the property names for the anonymous type in `new {f, b}` like this: `new { foo = f, bar = b}`. – devuxer Jan 07 '10 at 21:37
  • Ah, yes... that's right of course :-) I usually don't bother with the property names when all I really care about is the strucuture. It's also a bit more in keeping with functional programming where you're usually dealing with a structural type system rather than a nominal one. As an aside, I think one of the reasons I personally enjoy functional programming so much is that it lets you avoid one of the two hardest problems in computer science (cache invalidation and naming things :-) ). Cheers... – Rodrick Chapman Jan 07 '10 at 21:49
  • I edited your answer to remove the errors we talked about because I want to mark it as the answer. – devuxer Jan 08 '10 at 19:49
1

select (f2, b2) => { f2.MissingProp = b2.MissingProp; return f2 }

jjacka
  • 523
  • 4
  • 9
  • This doesn't work. It doesn't know that it should be passing in `f` and `b`. I also get the same error as if I use () => { ... } (see my update). – devuxer Jan 07 '10 at 20:29
1

Rewrite this with Lambda syntax.

var vf2 = foos.Join(bars, f => f.id, b => b.id, (foo, bar) => { foo.MissingProp = bar.MissingProp; return foo; });

If you need explanation of this syntax, let me know.

Stan R.
  • 15,757
  • 4
  • 50
  • 58
  • For some reason, this causes an error with the join clause (even though I didn't change the join clause): *The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'.* – devuxer Jan 07 '10 at 20:11
  • thats because when you do select you are telling it which type to return, select () assumes you are returning a delegate type. I think you should stick with your method. – Stan R. Jan 07 '10 at 20:22
  • Hmm...maybe it just can't be done. But why can't it infer the return type from `return f;`? It knows f is a `Foo`. – devuxer Jan 07 '10 at 20:33
  • @Dan, thats because select () is implying select new delegate, not select new Foo. – Stan R. Jan 07 '10 at 20:38
  • Wow, it does work with extension methods. I really hate the readability of the `Join()` extension method, but it works. Thanks for your help. – devuxer Jan 07 '10 at 20:54
  • @Dan, yeah the readability is not great..but it does what you ask it to. If you want readability then you should either stick with the method or use a foreach loop like suggested. Good luck :) – Stan R. Jan 07 '10 at 20:57
1

If the compiler isn't able to infer the correct type to pass to a lambda, you can of course specify the type yourself.

This should work fine:

select
    (Foo f2, b) => { f2.MissingProp = b.MissingProp; return f2; }

Note that as you've already noticed, you cannot reuse fand hope that it will retain its meaning. This is a new method signature, with new parameters, so you need to use a distinct name for it.

When you do, you notice that the compiler isn't able to figure out by itself what type the first argument should be, but you can specify it, like above.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • As for the incomplete code, I forgot my return statement. Just fixed it. – devuxer Jan 07 '10 at 21:04
  • You've also missed the end semicolon after the Linq query. You should always strive to copy and paste what you have in your code, not try to simplify it. You'd be amazed at what people here is able to read of code :) – Lasse V. Karlsen Jan 07 '10 at 21:06
  • Not able to get your solution to work. Even if I use `(Foo f2, Bar b)` instead of `(Foo f2, b)`, I still get flagged for my join clause. – devuxer Jan 07 '10 at 21:20
  • I agree about copy/paste...in this case, there was proprietary info in my code, however. – devuxer Jan 07 '10 at 21:21