-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathMore Effective LINQ
658 lines (574 loc) · 26.7 KB
/
More Effective LINQ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
Module 1: Course Overview
Constructing LINQ pipeline
Extending LINQ
Optimizing performance
Test and debug LINQ code
Goal: Apply LINQ in all situations and expend & optimize accordingly
Module 2: Discovering the Power of LINQ
Why LINQ is Awesome
Started in C# 3 (2007)
Language features LINQ is based on:
Lambda Expression
Extension Methods
Anonymous Types
Query Expression Syntax
Generics
"yield" & "var" keyword
Lambda Expressions
pass anonymous functions into methods
work best as short one-line function
eg.
customers.Where(c => c.Email != null)
// filter a list of customers w/ Email
Extension Methods
Extend any existing type - even if not created by user - by adding methods
Extension methods
must be static methods of a static class
must use "this" keyword in method declaration
class where the method xetends from is in method declaration
eg.
static class StringExtensions
{
public static string Shout(this string s)
{
return s.ToUpper() + "!!!!";
}
}
The library of framework methods that power LINQ are all extension methods on IEnumerable<T>
For IEmumerable<T>, LINQ extension methods are available
Many extension methods also return IEmumerable<T>, they can be chained into pipelines
LINQ pipelines should make code more readable, not less
Anonymous Types and the 'var' Keyword
"var": compiler infers type
eg. var dict = new Dictionary<string, Customer>();
"var": allows Anonymous Types
eg. var x = new { Author = "Mark Seemann", Title = "Dependency Injection in .NET" };
Anonymous Type with EXACT same properties are consider of same type
eg. var y = new { Author = "Martin Fowler", Title = "Patterns of Enterprise Architecture" }
// same type as var x above
var books = new[] {x, y}; // array of the smae Anonymous Type
Anonymous Type infer property names automatically
eg.
var author = "Adam Nathan";
var title = "WPF 4"
var book = new { author, title }; // but the property name is now lower case...
For LINQ, Anonymous Type is used to pass state through different stages of LINQ pipeline
preferable to tuples
Query Expression Syntax
Keywords introduced the same time as LINQ
Similar to SQL syntax
eg.
var query =
from c in customers
group c by c.Country into countryGroup
orderby countryGroup.Key
select countryGroup;
The syntax or LINQ is not only for DB
LINQ to Entities (EF): convert into SQL with Expression Tree
LINQ to Objects: an in-memory collection of objects
like list of strings or array of customers
Query Expression Syntax VS. Extension Methods (chained)
special cases for each approach
Generics and the 'yield' Keyword
Generics: Create methods and classes
that are NOT limited to only work with a specific type
eg. List<T> makes list of any type
Extend LINQ with customized generic method
eg.
// generic method that can operate on an IEnumerable<T>
public static IEnumerable<T> Double<T>(this IEnumerable<T> source)
{
foreach (var s in source)
{
yield return s;
yield return s;
// Whenever returning an IEnumerable sequence
// call "yield return" for each element of the sequence to be emitted
// use "yield break" to exist method early
}
}
Expression Trees: turn Lambda Expressions into data structure for inspection or manipulation
Collections are Everywhere
LINQ can be used w/ collections or sequences of data
In-memory objects
DB queries
Algorithmically generated data
LINQ brings functional programming style into C#
Introducing LINQPad
What to Expect in the Rest of this Course
Different "best practices" of LINQ
Mod 2: Thinking in patterns
- spot place to apply LINQ
Mod 3: Unleashing the Power of Pipelines
- chaining LINQ operators and extension methods
Mod 4: Writing Clean and Readable Code
- write LINQ pipelines that are easy to understand and maintain
Mod 5: Extending LINQ
- customized LINQ extension methods & use 3rd party extensions
Mod 6: Avoiding unnecessary work
- avoid code execution unless really need to
Mod 7: Optimizing Performance of LINQ queries
Mod 8: Test & Debug effectively w/ LINQ
Mod 9: Embracing functional style
- identify key functional concepts
Module 3: Thinking in Patterns
Module Introduction
Looping through collections
Switch from into imperative style (how to do) to declarative style (what to do)
Declarative style: expresses intent with succinct code
LINQ Challenge - Motorsport Scores
// calculate total score without the lowest 3
// starting with ...
"10, 5, 0, 8, 10, 1, 4, 0, 10, 1".Split(',')
// return string array
// My approach:
// 1. Convert string array into List of integer with Select()
// 2. Sort the List
// 3. Remove the last 3 items
// 4. Sum up the list
var scores = "10,5,0,8,10,1,4,0,10,1".Split(',');
var scoresAsInt = scores.Select(s => int.Parse(s));
var scoresAsOrderedIntWithoutLowest3 = scoresAsInt.OrderBy(i => i).Skip(3);
var totalScoreWithoutLowest3 = scoresAsOrderedIntWithoutLowest3.Sum();
Console.WriteLine(totalScoreWithoutLowest3);
// solution: as C# expression in LINQPad
"10, 5, 0, 8, 10, 1, 4, 0, 10, 1".Split(',')
.Select(int.Parse)
.OrderBy(n => n)
.Skip(3)
.Sum()
// This LINQ pipeline contains following:
// 1. turn strings into integers with Select(int.Parse)
// 2. Sort integer in ascending order - use element itself as key for sorting with OrderBy(n => n)
// 3. Discard lowest 3 scores with Skip(3)
// 4. Sum all numbers with Sum()
Get to Know the Available LINQ Extensions
MSDN -> System.Linq / Enumerable / Methods
know what are available in LINQ to simplify code
like takeWhile
Spot the Pattern #1 - Filtering Collections
Filter with Where() taking Lambda Expression
eg. Iterate over customer with Non-null or Non-empty Email property
foreach (var c in customers.Where(c => !String.IsNullOrEmpty(c.Email)))
{...}
Filter with query expression syntax
eg.
foreach (var customer in
from c in customers
where !String.IsNullOrEmpty(c.Email)
select c)
{ ... }
Spot the Pattern #2 - Finding One Item
Identify order by spacific order Id
guess: use Single() -
return the ONLY element that satisfied condition
throw InvalidOperationException if none or more than one elements are found
Recommandation #1: FirstOrDefault() -
find first item in collection that matches a particular condition or
return null or default value if none found
eg. return first order with matching id
Order orderToRefund = orders.FirstOrDefault(o => o.Id == orderId);
recommandation #2: First()
find first item in collection that matches a particular condition or
throw InvalidOperationException if none found
eg.
Order orderToRefund = orders.First(o => o.Id == orderId);
Spot the Pattern #3 - True for Everything?
Any() determines whether any element exists or satisfies a condition
return true if any element pass the condition
return flase otherwise
eg. Set the flag to true if any order was refunded
bool anyRefunded = orders.Any( o => o.Status == "Refunded");
All() determines whether all elements satisfy a condition
return true if sequence is empty OR all elements in sequence pass the condition
return flase otherwise
eg. Set flag to flase if NOT all orders are delivered
bool allDelivered = orders.All( o => o.Status == "Delivered");
Spot the Pattern #4 - Transforming Objects
Select() maps each element and return IEnumerable<T> of the same size
eg. Convert an array of file path to an IEnumerable<T> sequence of file size for each file
paths.Select( p => new FileInfo(p).Length )
Apply ToList(), ToArray(), ToDictionary() to create List, array or Dictionary from IEnumerable
eg. Create Dictionary form IEnumerable sequence
paths.Select(p => new FileInfo(p)).ToDictionary( p => p.Name, p => p.Length);
// Name is used as key and Length is used as value
Spot the Pattern #5 - How Many like This?
Count()
returns the number of elements that pass the given condition
OR given no lambda expression, returns the total number of elements in sequence
eg. return the number of refunded orders
orders.Count( o => o.Status == "Refunded" )
Sum()
returns sum of values return from lambda expression given to each element
eg. Sum of orders' amount
orders.Sum( o => o.Amount )
Spot the Pattern #6 - Grouping Things
GroupBy()
given lambda expression that returns key for grouping
returns an IEnumerable sequence of IGrouping objects, each with a key (specified by lambda expression)
eg.
orders.GroupBy(o => o.CustomerId)
// retuens Enumerable sequence of IGrouping objects
// Each IGrouping object has a key for the group
// The key is specified by o.CustomerId
// The IGrouping object also contains Enumerable sequence of the CustomerId
To turn results of GroupBy() into Dictionary
ToDictionary() takes two lambda expressions:
1st returns value for dictionary key
2nd returns value for dictionary value
Each of the IGrouping objects can also be converted into list for the value of dictionary
eg.
orders.GroupBy(o => o.CustomerId)
.ToDictionary(g => g.Key, g => g.ToList())
// group key is set to be dictionary key
// group contents are converted into List and used as dictionary value
Using Productivity Tools to Spot Patterns
ReSharper
LINQ Challenge Solution - Motorsport Scores
See "LINQ Challenge - Motorsport Scores" above
Module Summary
Learn to see repeating patterns, especially when dealing with collections or sequences
LINQ simplify code by replacing imperative code to expressive declarative code
Find and use productivity tools like ReSharper
LINQ extension methods can be chained into pipeline to solve multi-step problem
Module 4: Unleashing the Power of Pipelines
Module Introduction
Chaining LINQ to make pipeline
Use cases of pipeline
Building blocks / Nodes of pipeline
Transforming Elements
Each stage of pipeline transforms data into the format that are more useful
Filtering Elements
Distinct() Remove duplications
OfType<T>() Only keep objects of Type T
Order of operations may change outcome drastically
Reducing Sequences
Take entire sequence and collapse it into one object
likw Count(), Sum(), All(), Any()
other example like String.Concat() method to turn IEnumerable<string> sequence into single string
* to 1 methods: IEnumerable<T> in, single value out
First(), Count(), Min(), FirstOrDefault(), Sum(),
Max(), Last(), All(), Any(), LastOrDefault(),
String.Concat(), Aggregate()
Generating Sequences
* to 1 method:
Enumerable.Range() takes start number (2) and length (4)
to create sequence of incrementing numbers: 2, 3, 4, 5
String.Split() split string into IEnumerable<string> sequence with delimit character
Enumerable.Repeat() repeats an element for specified number of times
Regex.Matches() returns all matches on RegEx
Expanding Sequences
SelectMany()
turns each element of input sequence into another sequence
then combine them all together into one sequence - that can be longer or shorter
Reordering Sequences
Example methods:
OrderBy() for ascending sorting
OrderByDescending() for descending sortig
ThenBy() follows OrderBy w/ a secondary sort order
Reverse() emits all inpout elements in reverse order
GroupBy()
Reordering methods may require the whole sequence in memory
- Reverse cannot return result until entire list is consumed
LINQ to Entities: Reordering and Grouping at DB
The Power of Pipelines
Solve complex problems w/ manay simple steps
LINQ operate on a Pull rather than Push modle - nothing go through pipeline unless result is requested
Request via:
foreach statement
LINQ extension method
LINQ Challenge - Album Duration
"2:54,3:48,4:51,3:32,6:15,4:08,5:17,3:13,4:16,3:55,4:53,5:35,4:24"
1. Turn input string into sequence of strings representing each track duration
2. Parse each string into TimeSpan by TimeSpan.Parse(t),
but prefix each token with "0:" to denote zero hour - TimeSpan("0:" + t)
3. Add Total time with Aggregate() and + operator
- Aggregate(TimeSpan.Zero, (t1, t2) => t1 + t2)
OR Aggregate((t1, t2) => t1 + t2)
"2:54,3:48,4:51,3:32,6:15,4:08,5:17,3:13,4:16,3:55,4:53,5:35,4:24"
.Split(',')
.Select(t => TimeSpan.Parse("0:" + t))
.Aggregate((t1, t2) => t1 + t2)
LINQ Challenge - Range Expansion
Expand the range
e.g. "2,3-5,7" should expend to 2, 3, 4, 5, 7
"1,2-5,7".Split(',')
.Select(s => s.Split('-'))
.Select(n => new {start = int.Parse(n.First()), end = int.Parse(n.Last())})
.SelectMany(r => Enumerable.Range(r.start, r.end - r.start + 1))
1. Select(s => s.Split('-'))
For element containing '-', return an array of two element
For element without '-', return an array of one element
2. Select(n => new {start = int.Parse(n.First()), end = int.Parse(n.Last())})
Project each element into an anonymous object
where "start" stores first element of a sequence and "last" stores the last element of a sequence
when the sequence has only one element, both properties stores the same value
3. SelectMany(r => Enumerable.Range(r.start, r.end - r.start + 1))
Enumerable.Range(r.start, r.end - r.start + 1) generate a integral numbers
of length (r.end - r.start + 1) from (r.start)
Additional constraints: overlapping ranges with unsorted sequence
By extending pipeline with more methods to sort and remove duplicate
eg. "6, 1-3, 2-4" should expand to "1, 2, 3, 4, 6"
"6, 1-3, 2-4"
.Select(s => s.Split('-'))
.Select(n => new {start = int.Parse(n.First()), end = int.Parse(n.Last())})
.SelectMany(r => Enumerable.Range(r.start, r.end - r.start + 1))
.OrderBy(r => r)
.Distinct()
1. OrderBy(r => r) sorts sequence in ascending order
2. Distinct() remove duplicates
Real World LINQ - Find in Files (DEMO)
Search w/in text file like diagnostics file
task: get (log) files containing specific token in file name
eg.
var logFiles = disgsContainer.ListBlobs("skypevoicechangerpro/2015/02", true)
.OfType<CloudBlockBlob>()
.Select(b => new {
Blob = b, Name = String.Join("-", b.Name.Split('/').Take(5)) + ".csv"})
.Where(b => !File.Exists(Path.Combine(downlodFolder, b.Name)));
1. disgsContainer.ListBlobs("skypevoicechangerpro/2015/02", true)
Get all the log files within that container with the specified prefix
2. OfType<CloudBlockBlob>()
Check every elements in collection for specified type,
if element is of the type, it will be ccast into the type specified,
else, it will be filtered out
3. Select(b => new {Blob = b, Name = String.Join("-", b.Name.Split('/').Take(5)) + ".csv"})
Create local file name that will download this file
4. Where(b => !File.Exists(Path.Combine(downlodFolder, b.Name)))
Filter out files that are downloaded already (exist locally)
task: Search for specific phrase in the files
eg.
// specify which folder the files are in
var myDocs = Environment.GetFolderPath(Environment.SpecialFolder.MyDocument);
var diagsFolder = Path.Combine(myDocs, "skypevoicechanger", "diags");
var fileType = ".csv"; // file type to search for
var searchTerm = "User Submitted"; //
Directory.EnumerateFiles(diagsFolder, fileType)
.SelectMany(file => File.ReadAllLines(file))
.Where(line => Regex.IsMatch(line, searchTerm));
// 1. return IEnumerable<String> where each String is path of ".csv" file in diagsFolder
// 2. return IEnumerable<String> where each String is a line from each file path given
// 3. return IEnumerable<String> where each String is a line containing searchTerm
task: Search for specific phrase in the files and Identify sources
eg.
// Rewrite linq as following
Directory.EnumerateFiles(diagsFolder, fileType)
.SelectMany(file => File.ReadAllLines(file))
.Select((line, index) => new {
File = file,
Text = line,
LineNumber = index + 1
})
.Where(line => Regex.IsMatch(line.Text, searchTerm));
// New edit: Select() takes lambda expression where inputs are line of text and zero based index
// Anonymous objects returned at the end each contains file path, line of text and line number
task: Search for specific phrase in the files and Identify sources, then take first 10 results
eg.
// Rewrite linq as following
Directory.EnumerateFiles(diagsFolder, fileType)
.SelectMany(file => File.ReadAllLines(file))
.Select((line, index) => new {
File = file,
Text = line,
LineNumber = index + 1
})
.Where(line => Regex.IsMatch(line.Text, searchTerm))
.Take(10); // Take only first 10 results
Real World LINQ - Parsing Log Files
Task: Extract specific information categories from files sucah as date, license number, isFirstTime
eg.
// files location and type
var myDocs = Environment.GetFolderPath(Environment.SpeialFolder.MyDocuments);
var diagsFolder = Path.Combine(myDocs, "SkypeVoiceChanger", "diags");
var fileType = "*.csv";
Directory.EnumerateFiles(diagsFolder, fileType)
.SelectMany(file => File.ReadAllLines(file)
.Skip(1)
.Select(line => line.Split(','))
.Where(parts => parts.Length > 8))
.Where(parts => Regex.IsMatch(parts[8], "Checking license for"))
.Select(parts => new
{
Date = DateTime.Parse(parts[0])
License = Regex.Match(parts[8], @"\d+").Value
FirstTime = Regex.Match(parts[9], @"True|False").Value
})
.Where(x => x.License.Length >= 6)
.Dump();
/*
LINQ pipeline
1. SelectMany() read all the lines from each file given
2. skip over the first line, cloumn names, in each file with Skip(1)
3. split each line by ',' b/s file type is CSV
4. filter out collection with 8 or less strings to avoid blank lines
5. search for collection with matching phrase on index 8
6. Extract specific information from each line / collection and store them in an anonymous object
Date = DateTime.Parse(parts[0])
License = Regex.Match(parts[8], @"\d+").Value
FirstTime = Regex.Match(parts[9], @"True|False").Value
7. Filter out object w/ License Length 6 or more
*/
Additional Task: Identify unique license aomong extracted records
eg.
// Continue from previous example
.Where(x => x.License.Length >= 6)
.Select(e => e.License)
.Distinct()
.Dump();
/*
LINQ pipeline
1. Select(e => e.License) takes only license number from each entry
2. Distinct() removes all duplicate
*/
Real World LINQ - Orphaned Project Files (DEMO)
Task: Locate Orphaned Project Files - not part of any C# project
eg.
// in Main()
var sourceFolder = @"C:\path\to\source\files";
var allCSharpFiles =
Directory.EnumerateFiles(sourceFolder, "*.cs", SearchOption.AllDirectories)
.Where(p => !p.Contains(@"\obj\"))
.Where(p => !p.Contains(@"\bin\"))
.ToList();
/*
LINQ pipeline
1. Directory.EnumerateFiles(sourceFolder, "*.cs", SearchOption.AllDirectories) find all C# files in project folder
2. Where(p => !p.Contains(@"\obj\")) filter out any path contain "obj" folder
3. Where(p => !p.Contains(@"\bin\")) filter out any path contain "bin" folder
4. ToList() convert sequence to a List
*/
var allCSharpFilesInProjects =
Directory.EnumerateFiles(sourceFolder, "*.csproj", SearchOption.AllDirectories)
.FindCSharpFiles()
.ToList();
/*
LINQ pipeline
1. Directory.EnumerateFiles(sourceFolder, "*.csproj", SearchOption.AllDirectories) find all csproj files (XML) in project folder
2. FindCSharpFiles() returns all C sharp files referenced by project
3. ToList() convert sequence to a List
*/
static class MyExtensions {
public static IEnumerable<string> FindCSharpFiles(this IEnumerable<string> projectPaths) {
string xmlNamespace = "{}";
return from projectPath in projectPaths
let
}
}
Module Summary
Module 5: Writing Clean and Readable Code
Module Introduction
Why Clean Code Matters
Clean Code by Robert C Martin - Code that is easy for human to understand
Programmer spend more time "reading" code than "writing" code
Does LINQ make code more readable?
- Lambda expression or anonymous object can be confusing
LINQ extension methods makes code readable with streamlined declarative code from imperative (by command) code
Declarative Code:
decimal maxAmount = orders.Max(o => o.Amount);
V.S. Imperative Code:
decimal maxAmount = 0M;
foreach (var order in orders)
{
if (order.Amount > maxAmount)
maxAmount = order.Amount;
}
LINQ can also complicate code by:
Building pipeline with too many stages and obscure the overall objective
Over-complicated lambda expression by nesting LINQ pipeline within lambda expressions that are already inside another LINQ pipeline
LINQ Challenge - Sort by Age
Input: a list of name and date of birth in a sting
Output: name and age in year
1. Split the input string by ';' into strings of name and DOB
2. Select to transform Array of strings into Array of Arrays, where each inner array contains
one name string and one DOB string
3. Select from each inner array to create an anonymous object containing
property "Name" as string and property "DOB" Datetime object.
Parse DOB string with DateTime.ParseExact() method.
Each anonymous object will be added into an IOrderedEnumerable sequeence
4. Sort the sequence by DOB in descending order - from latest to earliest date
5. Project into another sequence of anonymous object with age instead of DOB utilize a method defined else where
LINQ pipeline:
"Jason Puncheon, 26/06/1986; Jos Hooiveld, 22/04/1983; Kelvin Davis, 29/09/1976; Luke Shaw, 12/07/1995; Gaston Ramirez, 02/12/1990; Adam Lallana, 10/05/1988"
.Split(';')
.Select(n => n.Split(','))
.Select(n => new { Name = n[0].Trim(), DOB = ParseDOB(n[1])})
.OrderByDescending(n => n.DOB)
.Select(n => { return new { Name = n.Name, Age = GetAge(n.DOB) }; });
// referenced "helper" methods
static DateTime ParseDOB(string DOB) => DateTime.ParseExact(DOB.Trim(), "d/m/yyyy", CultureInfo.InvariantCulture);
static int GetAge(DateTime DOB)
{
DateTime today = DateTime.Today;
int age = today.Year - DOB.Year;
if (DOB > today.AddYears(-age)) age--;
return age;
}
LINQ Challenge - Bishop Moves
Given a bishop piece is on c6 square of the board
Return all the possible squares it can move to
General approach:
enumerate over all 64 squares and filter out the ones that bishop cannot move to
Ranks (1-8) are rows and files (a-h) are columns
1. Enumerable.Range('a', 8) returns sequence from 'a' up to 8 char total [a,...,h], represented in ASCII value
2. SelectMany(x =>
Enumerable.Range('1', 8),
(f, r) => new { File = (char)f, Rank = (char)r }
)
For each file, 8 ranks are created, this expression will produce 64 anonymous objects
(one for each position on the board)
3. Where(x => Math.Abs(x.File - 'c') == Math.Abs(x.Rank - '6'))
Filter out any squares that are not along the two diagonal lines crossing at c6
4. Where(x => x.File != 'c')
Filter out starting position
5. Select(x => string.Format($"{x.File}{x.Rank}"))
Format the sequence
Using the LINQ Query Expression Syntax
LINQ Challenge - Longest Book
Module Summary
Module 6: Extending LINQ
Module Introduction
Demo: Creating an Extension Method
Demo: Concatenating Strings
Demo: Creating a MaxBy Extension Method
LINQ Challenge: Counting Pets
LINQ Challenge: Swim Length Times
Demo: Using MoreLINQ's Batch Extension Method
LINQ Challenge: Counting Consecutive Sales
Module Summary
Module 7: Avoiding Unnecessary Work with Laziness
Module Introduction
Deferred Execution
Demo: RSS Downloader
Breaking Out Early
Avoiding Multiple Enumeration
Multiple Enumeration and Databases
Multiple Enumeration and Correctness
Returning IEnumerable<T>
Module Summary
Module 8: Optimizing Performance
Module Introduction
When Should You Optimize?
Is LINQ Always the Right Choice?
Speeding up LINQ with LinqOptimizer and PLINQ
Understanding the Implementation
Optimizing LINQ to Entities
Demo: Avoiding Returning too Much Data
Demo: Avoiding Select N+1
Module Summary
Module 9: Testing and Debugging Effectively
Module Introduction
Demo: Debugging LINQ Queries in Visual Studio
Demo: Pipeline Tracing with Extension Methods
Testing LINQ Queries
Testing LINQ to Entity Framework
Exception Handling in LINQ Queries
Demo: Suppressing Errors in LINQ Pipelines
Module Summary
Module 10: Embracing a Functional Style
Module Introduction
Declarative Code
Chaining Functions
Higher Order Functions
Being Lazy
Avoiding Side Effects
Course Summary and Bonus Content