tags:

views:

154

answers:

4

I understand that when the C# compiler sees a linq query comprehension, it basically does a straight translation to the corresponding Linq Extension methods and lambdas. i.e.

from x in list
select x.property

gets translated to:

list.Select(x => x.property)

my question is what do let clauses get translated to. for example how would this get translated by the compiler.

from x in list
let v = SomeComplexExpressionDependingOnx
select v

(p.s. i know this could be reduced to just select SomeComplexExpressionDependingOnx but i want to know how this is done in general)

Thanks!

A: 

Just a guess since I rarely use query syntax:

list.Select(x => new { v = SomeComplexExpressionDependingOnx(x) });

The let is just assigning a new var v the select is returning it.

It could also be the following if you didn't want an anon object with v in it:

var v = list.Select(x => SomeComplexExpressionDependingOnx(x));
Kelsey
Wow negative votes for giving an explination? At least post why or you down vote doesn't help informing me of what I could do differently.
Kelsey
Your guess is wrong. The OP's code will not create an anonymous type.
Matthew Flaschen
@Matthew Flaschen interesting, if it doesn't then how is `select v` valid? isn't `v` storing the result of `SomeComplexExpressionDependingOnx`? I haven't used it ever before so it could have been a bad assumption but interested to know the why now like OP. I guess it's not anonymous since `SomeComplexExpressionDependingOnx` would be returning a known type?
Kelsey
@Kelsey you can write any expression after select. In particular, you could write `select x+2`. Does it mean that an anonymous type will be created with a single member named `x+2`? Think.
Fyodor Soikin
Yes, v has the result of `SomeComplexExpressionDependingOnx`. However, there is no reason to create an actual anonymous type with a `v` field.
Matthew Flaschen
@Fyodor Soikin yes I got it. I realize it as I was typing the other edit :) Will adjust as there is no point leaving an incorrect bit in my response.
Kelsey
A: 
list.Select(x => SomeComplexExpressionDependingOnx );

In general, let basically works as a readonly variable holding a range.

Matthew Flaschen
+11  A: 

In this particular case, it gets translated to:

list.Select( x => SomeComplexExpressionDependingOnx );

But there may be a more complex case, such as:

from x in list
let v = SomeComplexExpressionDependingOnx
where v > 10 && v+5 < 50 && SomeFunc(v) == "str"
select x

Will translate to:

list.Where( x => 
    {
        var v = SomeComplexExpressionDependingOnx;
        return v > 10 && v+5 < 50 && SomeFunc(v) == "str";
    }
)

In other words, the let keyword is a way to minimize and/or optimize your query. That is, without the let keyword you would have to write:

from x in list
where
    SomeComplexExpressionDependingOnx > 10 &&
    SomeComplexExpressionDependingOnx+5 < 50 &&
    SomFunc(SomeComplexExpressionDependingOnx) == "str"
select x

Resulting in possible triple evaluation of the same expression.

Update, following a question in comment.

First, what's so scary about "block expressions"? They're just a shorthand for arbitrary delegate. That is, the following expression:

Func<string,int> f = 
    s =>
    {
        var ln = s.Length;
        return ln/2;
    }

Is equivalent to the following:

int CompilerGeneratedMethodIdentifier0( string s )
{
    var ln = s.Length;
    return ln/2;
}

...

Func<string, int> f = new Func<string, int>( CompilerGeneratedMethodIdentifier0 );

Second, what's so special about "block expressions"? Did you know that mmm... let's call them "non-block" expressions also expand to the very same code? That is, the simple code new Func<string,int>( s => return s.Length/2 ) is absolute equivalent to:

int CompilerGeneratedMethodIdentifier0( string s )
{
    return ln/2;
}

...

new Func<string, int>( CompilerGeneratedMethodIdentifier0 );

Third, what's so non-linqy about "block expressions"? LINQ uses delegates all over the place, and it doesn't really matter to LINQ what exact shortcut you use to represent those delegates.

In particular, your expression from a in list where a.SomeProp > 10 select new { A = a, B = a.GetB() } gets translated into the following:

class AnonymousType0
{
    public MyClass A { get; set; }
    public othertype B { get; set; }
}

bool WhereFunc0( MyClass a )
{
    return a.SomeProp > 10;
}

AnonymousType0 SelectResultFunc0( MyClass a )
{
    AnonymousType0 result = new AnonymousType0();
    result.A = a;
    result.B = a.GetB();
    return result;
}

...

list
    .Where( new Func<MyClass,bool>( WhereFunc0 ) )
    .Select( new Func<MyClass,AnonymousType0>( SelectResultFunc0 ) );

Fourth, to get understanding like this, one can just play with the language and think. Use one's brain, that is.

And fifth, if the previous advice doesn't work for you for one reason or another, you always have .NET Reflector. Very useful tool, everybody should have one.

Fyodor Soikin
huh, i never would've expected it to resolve to a block expression, Block Expressions just don't seem very linq'y to me. Do you have a source for this, or is it just a guess?
luke
I've updated the answer to address your comment.
Fyodor Soikin
@Fyodor Soikin thanks for the hard work, i guess when i said that i didn't think block expressions were very "linq'y", it was because i associate (incorrectly) block expressions with side-effect causing expressions (which definitely isn't linq'y), but clearly i was wrong :). Thanks again
luke
I can only give one +1 :( I heartily second point four: play with your language, a truth for any (programming) language.
cfern
Thank you, sir! :-)
Fyodor Soikin
+3  A: 

Take a look at LINQPad, you can write the query and hit the lamba symbol to see what the output will look like. For example I took this query:

var names = new[] { "Tom", "Dick", "Harry", "Mary", "Jay" }.AsQueryable();

var results = 
    from n in names
    let n1 = String.IsNullOrEmpty(n)
    select n1;

results.Dump();

And it output the following:

System.String[]
   .Select (
      n => 
         new  
         {
            n = n, 
            n1 = String.IsNullOrEmpty (n)
         }
   )
   .Select (temp0 => temp0.n1)

So it does indeed look like the let is translated to a temp value as anonymous, and then consumed in the outer select statement.

I love LINQPad for the ability to write the query and see how it would translate.

Jason Short
@Jason Short I thought there had to be an anonymous type or else where would the v be stored temporarily...
Kelsey