0

Is there an imperative alternative to the let keyword in C# LINQ?

E.g. in the MSDN documentation there is the declarative LINQ sample:

var earlyBirdQuery =
            from sentence in strings
            let words = sentence.Split(' ')
            from word in words
            let w = word.ToLower()
            where w[0] == 'a' || w[0] == 'e'
                || w[0] == 'i' || w[0] == 'o'
                || w[0] == 'u'
            select word;

And I would like to write it in an imperative manner. Is it possible?

I know that I can do the following:

var earlyBirdQuery = sentence
.Select(s => s.Split(' '))
.Select(w => w.ToLower())
.Where(w => w[0] == 'a' || w[0] == 'e' || w[0] == 'i' || w[0] == 'o' || w[0] == 'u')
.Select(w => w);

Does it mean that the Select in the imperative manner has the effect of the from + let in the declarative manner or are there other ways to mimic the let and the Select is not exactly the same as the from + let?

qqqqqqq
  • 1,831
  • 1
  • 18
  • 48
  • 4
    Does this answer your question? [Code equivalent to the 'let' keyword in chained LINQ extension method calls](https://stackoverflow.com/questions/1092687/code-equivalent-to-the-let-keyword-in-chained-linq-extension-method-calls) – Pethical Mar 10 '20 at 19:42
  • 2
    The C# specification describes how a "let" clause is turned into imperative code; you might consider reading that part of the specification if you are curious about how the transformation works. You might also wish to read my article on one of the tricky aspects of this transformation: https://ericlippert.com/2014/07/31/transparent-identifiers-part-one/ – Eric Lippert Mar 10 '20 at 20:14
  • @Pethical, yeah. It does. Thank you. – qqqqqqq Mar 10 '20 at 20:27
  • 1
    @EricLippert, oh. I will probably bookmark the whole blog. Thank you. (y) – qqqqqqq Mar 10 '20 at 20:30

1 Answers1

4

Here, you can use SelectMany that flattens nested sequences.

char[] vowels = new[] { 'a', 'e', 'i', 'o', 'u' };
var earlyBirdQuery = sentence
    .SelectMany(s => s.Split(' '))
    .Select(w => w.ToLower())
    .Where(w => vowels.Contains(w[0]));
// Result contains all words from all sentences beginning with a vowel.

The first let in your query is not really necessary, as you could simply write from word in sentence.Split(' ').

You have two from clauses. They act much like two nested C# for-statements. SelectMany unwraps the inner one. There are also situations, where you query an object having a collection property where you can use SelectMany. E.g.:

// Select all files from all subdirectories of the temp directory.
var directory = new DirectoryInfo(@"c:\temp");
IEnumerable<string> files = directory.GetDirectories()
    .SelectMany(d => d.GetFiles())
    .Select(f => f.FullName);

or, with an other overload of SelectMany having a result selector:

var directory = new DirectoryInfo(@"c:\temp");
IEnumerable<string> files = directory.GetDirectories()
    .SelectMany(d => d.GetFiles(), (d, f) => f.FullName)

In other situations, you can store intermediate results by creating anonymous types. E.g.:

string[] list = new[] { "A", "B" };
var result = from x in list
                let y = x.ToLowerInvariant()
                where y == "a"
                select x + " " + y;

with the extension method syntax:

string[] list = new[] { "A", "B" };
var result = list
    .Select(x => new { x, y = x.ToLowerInvariant() })
    .Where(a => a.y == "a")
    .Select(a => a.x + " " + a.y);

In some cases you can use a statement block to replace a let:

...
.Where(word => {
    string lower = word.ToLower();
    return lower.Contains("x") || lower.Contains("y");
})
...
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • 1
    These days I'd use a `ValueTuple` instead of an anonymous type to get the `let` behavior, i.e. `strings.Select(s => (sentence: s, words: s.Split(' ')))` – dbc Mar 10 '20 at 20:23
  • 1
    Yes, you can do this on LINQ to objects; however, I do not know, whether LINQ to EF Core currently supports ValueTuples. It supports anonymous types. – Olivier Jacot-Descombes Mar 10 '20 at 20:27