views:

363

answers:

4

I am new in the functional side of C#, sorry if the question is lame.

Given the following WRONG code:

var jobSummaries = from job in jobs
                   where ...
                   select new 
                   {
                        ID = job.ID,
                        Description = job.Description,
                        FileName = (job) => {
                                  // primitive logic not 
                                  // worth to become a named method
                                  try { return job.Files[0].LocalName); }
                                  catch { return null as string; }
                                 }
                   };

This code produces the following justified compiler error:

cannot assign lambda expression to anonymous type property

The code above would set the delegate to the FileName property. But that is not my aim. I want the code work like this but without naming the method:

var jobSummaries = from job in jobs
                   where ...
                   select new 
                   {
                        ID = job.ID,
                        Description = job.Description,
                        FileName = this.ExtractFileName(job)
                   };

...
private string ExtractFileName(Job job)
{
     try { return Path.GetFileName(job.Files[0].LocalName); }
     catch { return null as string; }
}

Any suggestions?

+2  A: 

The call an anonymous function directly, this works:

int result = new Func<int, int>( (int i) =>{ return i + 5; } ).Invoke(3);
// result = 8

But I agree, int result = (i => i + 5)(3); would be way coler =)

Jens
+2  A: 

As far as I know, you can't inline lambda expressions like that because a lamda expression is an instance itself (of the type Expression<Func<T>> or similar).

However, you can do this (updated with calculation of fileName, since this is now provided by the OP):

var jobSummaries = from job in jobs
                   where ...
                   let fileName = job.Files.Select(f => f.LocalName).FirstOrDefault()
                   select new 
                   {
                        ID = job.ID,
                        Description = job.Description,
                        FileName = fileName
                   };

Notice the use of the let keyword, that lets you extract the filename from the job variable directly inside the LINQ expression.

Mark Seemann
I just tried. I cannot set lambda expression as a local variable with 'let'. I updated the sample code. I have a try/catch which makes direct computing difficult.
GarbageGuy
No, you can't assign a lambda expression to `fileName`. Just write the code: `let fileName = Path.Combine(job.Folder, job.File)` or whatever logic you need...
Mark Seemann
Why the anonymous downvote?
Mark Seemann
@GarbageGuy: I updated my answer to reflect your updated question, now that I know the algorithm you need. Notice that a try/catch isn't necessary.
Mark Seemann
Maybe I miss something, but I do not get the point. Should the logic be computable as a single expression, I could inline it directly in the property assignment. But it contains try/catch (see the updated code sample), so it works neither with 'let' nor with direct assignment.
GarbageGuy
In your particular example, the try/catch construct isn't necessary (see my updated answer for a better solution). If your question a placeholder for something more complicated, then you are correct that this doesn't solve your problem. You could go with one of the other solutions offered here, but personally I would think that the code would be clearer if you encapsulate complex code in a separate method or class. YMMV.
Mark Seemann
Actually, FirstOrDefault() is not applicable because the array may contain 0..N elements, so that might throw exception, too. But regardless, the underlying question was how to inline anonymous method and not about how to avoid it. Anyway, thank you for your contribution, I did learn from it!!!
GarbageGuy
FWIW, FirstOrDefault returns the default (null, in this case) if there are no items in the list, so it wouldn't throw an exception even if the array is empty. Try it :)
Mark Seemann
A: 

The compiler is complaining because you are not calling your lambda function, you are defining it. If the compiler would let you, you'd have a FileName property that is a function rather than a value.

If you can write your "primitive logic" as an expression, you can write that directly in the assignment statement.

jdv
+2  A: 

The MSDN page for CS0828 does indeed say

An anonymous type cannot be initialized with ... a method group or anonymous function.

Now, I'm not qualified to comment on why this should be, but I do noe eit but goes on to suggest

... add a type declaration to the left side of the assignment ...

and this should work for you:

select new 
{
    ID = job.ID,
    Description = job.Description,
    FileName = (Func<Job, string> ((job) => {
                              // some primitive logic extracting 
                              // file name from job object that is not 
                              // worth to become a named method
                        })) (job)
};

Explicitly tell the compiler the type of the lambda, and it will be happy.

AakashM
This code produces the following compiler error:'System.Func<Job,string>' is a 'type' but is used like a 'variable'
GarbageGuy
He 's missing a "new" before the "Func<...>".
Jens