In general, Let allows creating a local variable at the LINQ query. Let has the same operation as to build local variable in a loop.
This example has the analogy between a loop and LINQ query.
Let is allowed only in LINQ queries, so it can’t be used in Lambdas.
Example code
This is the classes we will use in our examples.
Shop class
- 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
- 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.
- 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 us the improvement in 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 in each shop is a pair.
Example without let,
- [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,
- [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
Encapsulate functionality
Another Let advantage is encapsulating functionality. If we do good work with Let in our LINQ queries, we only need to change the code at one place in our code for refactoring.
Example without let,
- [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,
- [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
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 with 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,
- [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,
- [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
The example with Let hast better performance than the without Let example, because the first one executes one time and the second one execues two time for each item.
We have added two tests to show method performance:
- [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}");
- }
Hide Copy Code
Output --> 17803.3522 miliseconds
- [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}");
- }
Hide Copy Code
Output --> 12497.8096 miliseconds
With Let is faster.