Click here to Skip to main content
15,889,527 members
Articles / Programming Languages / C#
Tip/Trick

Let in LinQ

Rate me:
Please Sign up or sign in to vote.
4.42/5 (10 votes)
8 Oct 2017CPOL2 min read 21.4K   73   11   12
Let in LINQ

Introduction

In general, Let allows to create a local variable at the LinQ query. Let has the same operation as to build local variable in a loop.

This example has analogy between loop and LinQ query:

Image 1

Let is allowed only in LinQ queries, so that it can’t use in Lambdas.

Example Code

These are the classes we will use in our examples.

Shop class:

C#
using System.Collections.Generic;

namespace LetKeyword
{
    public class Shop
    {
        public int               Id    { get; set; }
        public string            Name  { get; set; }
        public IEnumerable<Sale> Sales { get; set; }
    }
}

Note: The Shop class has an IEnumerable<Sale> property with the associated sales.

Sale class:

C#
using System;

namespace LetKeyword
{
    public class Sale
    {
        public int      Id     { get; set; }
        public DateTime Date   { get; set; }
        public decimal  Amount { get; set; }
    }
}

The ShoppingDB class is a virtual DB process:

C#
using System;
using System.Collections.Generic;

namespace LetKeyword
{
    public static class ShoppingDB
    {
        public static IEnumerable<Shop> GetShops()
        {
            var result = new List<Shop>()
            {
                new Shop
                {
                    Id    = 1,
                    Name  = "Shop 1",
                    Sales = new List<Sale>()
                    {
                        new Sale{ Id = 1,  Date = new DateTime(2017,01,02), Amount =  1520m },
                        new Sale{ Id = 8,  Date = new DateTime(2017,01,26), Amount =   500m },
                        new Sale{ Id = 25, Date = new DateTime(2017,02,15), Amount =  8900m },
                        new Sale{ Id = 26, Date = new DateTime(2017,02,28), Amount = 40000m },
                        new Sale{ Id = 39, Date = new DateTime(2017,03,02), Amount = 75000m }
                    }
                },
                new Shop
                {
                    Id    = 2,
                    Name  = "Shop 2",
                    Sales = new List<Sale>()
                    {
                        new Sale{ Id = 2,  Date = new DateTime(2017,01,06), Amount =     10m },
                        new Sale{ Id = 3,  Date = new DateTime(2017,01,08), Amount =   3000m },
                        new Sale{ Id = 11, Date = new DateTime(2017,02,11), Amount = 100000m },
                        new Sale{ Id = 12, Date = new DateTime(2017,02,12), Amount = 515000m },
                        new Sale{ Id = 42, Date = new DateTime(2017,03,12), Amount =     25m },
                        new Sale{ Id = 43, Date = new DateTime(2017,03,12), Amount =    200m },
                        new Sale{ Id = 52, Date = new DateTime(2017,03,16), Amount =    300m }
                    }
                },
                new Shop
                {
                    Id    = 3,
                    Name  = "Shop 3",
                    Sales = new List<Sale>()
                    {
                        new Sale{ Id = 13,  Date = new DateTime(2017,02,12), Amount = 2500m },
                        new Sale{ Id = 14,  Date = new DateTime(2017,02,12), Amount = 3000m }
                    }
                },
                new Shop
                {
                    Id    = 4,
                    Name  = "Shop 4",
                    Sales = new List<Sale>()
                    {
                        new Sale{ Id = 15, Date = new DateTime(2017,01,13), Amount =  79000m },
                        new Sale{ Id = 16, Date = new DateTime(2017,01,13), Amount =   6000m },
                        new Sale{ Id = 53, Date = new DateTime(2017,03,17), Amount = 145000m },
                        new Sale{ Id = 54, Date = new DateTime(2017,03,17), Amount =   5000m },
                        new Sale{ Id = 55, Date = new DateTime(2017,03,18), Amount =  37800m },
                        new Sale{ Id = 56, Date = new DateTime(2017,03,19), Amount =  11200m },
                        new Sale{ Id = 57, Date = new DateTime(2017,03,26), Amount =  22580m },
                        new Sale{ Id = 58, Date = new DateTime(2017,04,01), Amount =   1000m },
                        new Sale{ Id = 59, Date = new DateTime(2017,04,02), Amount =   9000m },
                        new Sale{ Id = 60, Date = new DateTime(2017,04,03), Amount = 990000m },
                        new Sale{ Id = 61, Date = new DateTime(2017,04,04), Amount =   8000m },
                        new Sale{ Id = 62, Date = new DateTime(2017,04,05), Amount =  52580m },
                        new Sale{ Id = 63, Date = new DateTime(2017,04,06), Amount = 558900m },
                        new Sale{ Id = 64, Date = new DateTime(2017,04,07), Amount =  88900m }
                    }
                }
            };

            return result;
        }
    }
}

Benefits of let

The main benefits of let are:

  • Reading compression code
  • Encapsulate functionality
  • Improvement performance

Reading Compression Code

The let keyword provides the improvement reading and compression code, because unit calls and calculations transform it in more readable.

In this example, we build a query where the shop has sales in March and the number of sales each shop is pair.

Example without let:

C#
[TestMethod]
public void ReadingCompressionCode_WithoutLet()
{
    var result = from shop in ShoppingDB.GetShops()

                 where shop.Sales.Any(s => s.Date.Month == 3) 
                    && shop.Sales.Count() % 2 == 0

                 select shop;
}

Example with let:

C#
[TestMethod]
public void ReadingCompressionCode_WithLet()
{
    var result = from shop in ShoppingDB.GetShops()

                 let hasMarchSales = shop.Sales.Any(s => s.Date.Month == 3)
                 let hasPairSales  = shop.Sales.Count() % 2 == 0

                 where hasMarchSales && hasPairSales

                 select shop;
}

Comparison:

Image 2

Encapsulate Functionality

Another let advantage is encapsulating functionality. If we do good work with let in our linq queries, we need to only change code in one place in our code for refactorings.

Example without let:

C#
[TestMethod]
public void EncapsulateFunctionality_WithoutLet()
{
    var result = from shop in ShoppingDB.GetShops()

                    where shop.Sales.Average(a => a.Amount) > 1000
                       && shop.Sales.Average(a => a.Amount) < 100000

                    select shop;
}

Example with let:

C#
[TestMethod]
public void EncapsulateFunctionality_WithLet()
{
    var result = from shop in ShoppingDB.GetShops()

                    let myAverage = shop.Sales.Average(a => a.Amount)

                    where myAverage > 1000 
                       && myAverage < 100000

                    select shop;
}

Comparison:

Image 3

With the let solution, in change case, only we will modify in one place, without let, we will modify in 2 or N places.

Improvement Performance

In the previous without let example, we have seen how we wrote the same code two times for the same goal. As we wrote the code two times, the code will be executed two times too with a lost performance.

This problem is greater if our query has a select extender method, because add another execution.

Example without let:

C#
[TestMethod]
public void ImprovementPerformance_WithoutLet()
{
    var result = from shop in ShoppingDB.GetShops()

                    where shop.Sales.Average(a => a.Amount) > 1000
                       && shop.Sales.Average(a => a.Amount) < 100000

                    select new
                    {
                        Id           = shop.Id,
                        Name         = shop.Name,
                        Sales        = shop.Sales,
                        SalesAverage = shop.Sales.Average(a => a.Amount)
                    };
}

Example with let:

C#
[TestMethod]
public void ImprovementPerformance_WithLet()
{
    var result = from shop in ShoppingDB.GetShops()

                    let myAverage = shop.Sales.Average(a => a.Amount)

                    where myAverage > 1000
                       && myAverage < 100000

                    select new
                    {
                        Id           = shop.Id,
                        Name         = shop.Name,
                        Sales        = shop.Sales,
                        SalesAverage = myAverage
                    };
}

Comparison:

Image 4

The example with let has better performance than the without let example, because the first executes one time and second two times for each item.

We have added two tests to show method performance:

C#
[TestMethod]
public void ImprovementPerformance_WithoutLet3()
{
    var stopWatch = new Stopwatch();
    stopWatch.Start();
 
    var data = Enumerable.Range(0, 30000 - 1).ToList();
 
    var result = (from s in data
 
                    where data.Average(a => a) > s
                    && data.Average(a => a) < 100
 
                    select new
                    {
                        Number = s,
                        Average = data.Average(a => a)
                    }).ToList();
 
    stopWatch.Stop();
 
    System.Diagnostics.Trace.WriteLine($"{stopWatch.Elapsed.TotalMilliseconds}");
}

Output --> 17803.3522 milliseconds

C#
[TestMethod]
public void ImprovementPerformance_WithLet4()
{
    var stopWatch = new Stopwatch();
    stopWatch.Start();
 
    var data = Enumerable.Range(0, 30000 - 1).ToList();

    var result = (from s in data
 
                    let average = data.Average(a => a)
 
                    where average > s
                        && average < 100
 
                    select new
                    {
                        Number = s,
                        Average = average
                    }).ToList();
 
    stopWatch.Stop();
 
    System.Diagnostics.Trace.WriteLine($"{stopWatch.Elapsed.TotalMilliseconds}");
}

Output --> 12497.8096 milliseconds

With Let is faster.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Cecabank
Spain Spain
MVP C# Corner 2017

MAP Microsoft Active Professional 2014

MCPD - Designing and Developing Windows Applications .NET Framework 4
MCTS - Windows Applications Development .NET Framework 4
MCTS - Accessing Data Development .NET Framework 4
MCTS - WCF Development .NET Framework 4

Comments and Discussions

 
SuggestionJust a small suggestion about aligning Pin
Soner Gönül9-Oct-17 22:37
Soner Gönül9-Oct-17 22:37 
GeneralRe: Just a small suggestion about aligning Pin
Juan Francisco Morales Larios10-Oct-17 5:34
Juan Francisco Morales Larios10-Oct-17 5:34 
PraiseRe: Just a small suggestion about aligning Pin
asiwel10-Oct-17 20:45
professionalasiwel10-Oct-17 20:45 
Hey, I enjoyed your article too. Seems I needed to upgrade though through Nuget to MSTestFramework 1.1.18 (instead of the 1.1.11 version in your download). My test results were very similar to those you reported. Maybe one should use LinQ queries more often!

However, I must disagree with Soner Gönül (in the same spirit Smile | :) . Earlier today, as a matter of fact, I finally downloaded that nice Code Alignment VS extension to play with. I've always tried to align my code (constantly fighting VS IDE in that respect to keep things aligned!) and thought it about time to try out the extension. Seems to work fairly well! I like it better aligned, myself.

(I also picked up another tool called "CodeMaid" (and actually used that to clean up all the double spaces, etc. in your download, Ha!). Apparently over 1m people have downloaded that so it must be useful (and free is better than Refactor).
PraiseRe: Just a small suggestion about aligning Pin
Juan Francisco Morales Larios11-Oct-17 9:07
Juan Francisco Morales Larios11-Oct-17 9:07 
QuestionInteresting Pin
CarelAgain8-Oct-17 16:50
professionalCarelAgain8-Oct-17 16:50 
AnswerRe: Interesting Pin
Juan Francisco Morales Larios8-Oct-17 21:38
Juan Francisco Morales Larios8-Oct-17 21:38 
QuestionExample without let incorrect Pin
i008-Oct-17 13:26
i008-Oct-17 13:26 
AnswerRe: Example without let incorrect Pin
Juan Francisco Morales Larios8-Oct-17 23:15
Juan Francisco Morales Larios8-Oct-17 23:15 
GeneralRe: Example without let incorrect Pin
i0010-Oct-17 20:44
i0010-Oct-17 20:44 
GeneralRe: Example without let incorrect Pin
Juan Francisco Morales Larios11-Oct-17 9:06
Juan Francisco Morales Larios11-Oct-17 9:06 
GeneralRe: Example without let incorrect Pin
i0011-Oct-17 15:23
i0011-Oct-17 15:23 
GeneralRe: Example without let incorrect Pin
Juan Francisco Morales Larios11-Oct-17 21:12
Juan Francisco Morales Larios11-Oct-17 21:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.