views:

306

answers:

5

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.

+2  A: 

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});
Rodrick Chapman
this wouldn't work either.
Stan R.
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).
DanM
See my edit please...
Rodrick Chapman
Second times the charm :) Well, except the second `from` should be a `join`. But, otherwise, this is my favorite solution so far.
DanM
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}`.
DanM
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
I edited your answer to remove the errors we talked about because I want to mark it as the answer.
DanM
+1  A: 

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

jjacka
this wouldn't work.
Stan R.
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).
DanM
+1  A: 

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.
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'.*
DanM
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.
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`.
DanM
Updated answer Dan.
Stan R.
@Dan, thats because select () is implying select new delegate, not select new Foo.
Stan R.
Wow, it does work with extension methods. I really hate the readability of the `Join()` extension method, but it works. Thanks for your help.
DanM
@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.
+5  A: 

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
Thanks +1. I had thought about using `foreach` but I guess I was trying to be too fancy :) This is a good solution.
DanM
Oops, one problem. This leaves me with an `IEnumerable<anonymous_type_foo_bar_mashup>` but I need an `IEnumerable<foo>`. I think if you add `return fooBar.Select(fb => fb.f);` to your answer, it will be correct.
DanM
You should also be able to do `var foobar = (....).ForEach(....); ` and one line it!
Chris Marisic
@DanThMan: edited it, but you should change the return type of AddMissingProp to make the original question fit to the edited answe :-)
Doc Brown
@Chris: ForEach works on Array<>, not on IEnumerable<>. And I don't think this will make it better readable.
Doc Brown
+1  A: 

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
As for the incomplete code, I forgot my return statement. Just fixed it.
DanM
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
removed last part of answer to reflect edited question.
Lasse V. Karlsen
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.
DanM
I agree about copy/paste...in this case, there was proprietary info in my code, however.
DanM