{"id":25654247,"url":"https://github.com/bert2/love-linq-hate-loops","last_synced_at":"2025-10-30T20:42:12.389Z","repository":{"id":54158120,"uuid":"152637153","full_name":"bert2/love-linq-hate-loops","owner":"bert2","description":"Dr. LINQlove or: How I Learned to Stop Looping and Love the Func","archived":false,"fork":false,"pushed_at":"2019-05-14T17:41:13.000Z","size":148,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-10T09:55:42.814Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bert2.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-10-11T18:27:02.000Z","updated_at":"2024-09-20T08:48:28.000Z","dependencies_parsed_at":"2022-08-13T07:50:56.470Z","dependency_job_id":null,"html_url":"https://github.com/bert2/love-linq-hate-loops","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bert2%2Flove-linq-hate-loops","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bert2%2Flove-linq-hate-loops/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bert2%2Flove-linq-hate-loops/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bert2%2Flove-linq-hate-loops/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bert2","download_url":"https://codeload.github.com/bert2/love-linq-hate-loops/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249252525,"owners_count":21238207,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-23T20:18:59.112Z","updated_at":"2025-10-30T20:42:07.353Z","avatar_url":"https://github.com/bert2.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Introduction\r\n\r\nLINQ has been around in C# for quite some time now. Still many programmers prefer explicit looping with `for` and `foreach` in situations that also could have been solved using LINQ operators.\r\n\r\nI claim that to every problem that seemingly requires loops there is a LINQ solution which is cleaner. As a matter of fact, I believe that loops should be avoided and only be used in rare special cases.\r\n\r\nThe following examples show LINQ solutions to common list transformation problems while pointing out the issues of using loops.\r\n\r\nInstall [LINQPad](https://www.linqpad.net/) in order to run the example files.\r\n\r\n```\r\nPS\u003e choco install linqpad5\r\n```\r\n\r\n### TL;DR\r\n\r\nJump straight to the [summary](#summary).\r\n\r\n### Disclaimer\r\n\r\nSome of my statements in this text might seem a bit absolute or even harsh. But after over 15 years of coding I've seen some pretty abhorrent nesting of loops in legacy code over and over again (including code I wrote myself) which leads to some frustration of course.\r\n\r\nThe sad part is though, that I've also seen the same nested loops happening in greenfield projects. Either because the developers are just used to this kind of code and don't know better, or because they feel inclined to take shortcuts due to pressure or laziness. And that's a big problem.\r\n\r\nWith this guide I'm hoping to do a small part in alleviating this problem, but take what I write here with a grain of salt.\r\n\r\n### Table of contents\r\n\r\n- [Introduction](#introduction)\r\n  - [TL;DR](#tldr)\r\n  - [Disclaimer](#disclaimer)\r\n  - [Table of contents](#table-of-contents)\r\n- [0. Why I hate loops](#0-why-i-hate-loops)\r\n- [1. The only acceptable `for` loop](#1-the-only-acceptable-for-loop)\r\n- [2. The only acceptable `foreach` loop](#2-the-only-acceptable-foreach-loop)\r\n- [3. Map](#3-map)\r\n- [4. Filter](#4-filter)\r\n- [5. Capture index](#5-capture-index)\r\n- [6. Flatten](#6-flatten)\r\n- [7. Carry index](#7-carry-index)\r\n- [8. Cross join](#8-cross-join)\r\n- [9. Inner join](#9-inner-join)\r\n- [10. Left join](#10-left-join)\r\n- [11. Right join](#11-right-join)\r\n- [12. Full join](#12-full-join)\r\n- [13. Conditional join](#13-conditional-join)\r\n- [14. Reduce](#14-reduce)\r\n- [15. Moving window](#15-moving-window)\r\n- [16. Tap](#16-tap)\r\n- [17. Modularization](#17-modularization)\r\n- [18. Laziness](#18-laziness)\r\n- [19. How to be lazy](#19-how-to-be-lazy)\r\n- [20. Debugging](#20-debugging)\r\n- [21. Summary](#21-summary)\r\n\r\n\u003ca name=\"0\"\u003e\u003c/a\u003e\r\n## 0. Why I hate loops\r\n\r\nCan you see what the `for` loop below does? No? Me neither. This just goes to show what ugly kind of things are possible with loops.\r\n\r\n```C#\r\nvar a = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };\r\n\r\nvar x = 0;\r\n\r\nfor (var i = 0; i \u003c a.Length; i++) {\r\n    if (i % 3 == 0 \u0026\u0026 i \u003e 0)\r\n        a[i - 1] += a[i];\r\n\r\n    if (a[i] % 2 == 0) {\r\n        --x;\r\n        continue;\r\n    }\r\n\r\n    a[i] += x;\r\n}\r\n\r\na.Dump();\r\n```\r\n\r\nOf course, it's also possible to write clean code using `for` loops, as well as it is possible to write dirty code using LINQ. However, LINQ promotes abstraction, modularization, and immutability more than loops do. LINQ does this by taking power away from you and in the beginning it might feel you are programming with your hands tied to your back. But in the long run you will end up with code that is easier to maintain.\r\n\r\nHave you ever considered the level of abstraction loops offer? It's actually astonishingly low. In essence they are just syntactic sugar for an increment and a branch instruction that both wrap the instructions of your loop body. Sure, it's efficient. But so is coding in assembly language. Because loops are so low-level, some people even say that [the `for` loop will be the new `goto`]( https://www.quora.com/Can-we-say-that-the-for-loop-will-be-the-new-goto-%E2%80%9D-given-the-increasing-popularity-of-functional-programming/answer/Tikhon-Jelvis?share=1\u0026srid=3SJB).\r\n\r\nThe lack of abstraction of loops forces readers to carefully track state in order to understand what your loop is doing. One cannot immediately tell whether the loop is for instance filtering or mapping the input list (or maybe it's doing both operations at once). With LINQ operators intent becomes more explicit, because a `Where()` will always filter and a `Select()` will always map.\r\n\r\nThe hard truth is though that most programmers, when given a problem that requires some kind of looping, will be fastest when coding it using `for`/`foreach` loops. I think that's because they already have an _abstract model_ of the problem in their heads and coding appears as the act of merely turning this model into _concrete instructions_. But that's where they are wrong. Because now it is up to the readers of their code to _reverse engineer_ that abstract model from a set of statements that really could do anything.\r\n\r\nThis is were LINQ helps. Using LINQ forces you to stay in the abstract realm more than loops do and delegates the dirty business with concrete instructions to the compiler.\r\n\r\nIn short: excessively relying on loops is like not writing tests. Sure, you will be faster now, but in the end your colleagues will hate you for it.\r\n\r\n\u003ca name=\"1\"\u003e\u003c/a\u003e\r\n## 1. The only acceptable `for` loop\r\n\r\nThe only acceptable `for` loop is the one that has only one statement in it's body and does not need curly braces. In fact it should be enforced that loops are defined without curly braces to make sure they always have single-statement bodies.\r\n\r\n```C#\r\nvoid Main() {\r\n    var foos = new[] { new Foo(), null, new Foo() };\r\n    EnsureAllInitialized(foos);\r\n    foos.Dump();\r\n}\r\n\r\nvoid EnsureAllInitialized(Foo[] foos) {\r\n    for (var i = 0; i \u003c foos.Length; i++)\r\n        foos[i] = foos[i] ?? new Foo();\r\n}\r\n\r\nclass Foo { }\r\n```\r\n\r\nMake the loop body a method in case it needs more statements. All dependencies should be parameters of that method.\r\n\r\nStill a for loop should only be used when the performance gain due to in-place editing of collections really is needed.\r\n\r\n\u003ca name=\"2\"\u003e\u003c/a\u003e\r\n## 2. The only acceptable `foreach` loop\r\n\r\nThe only acceptable `foreach` loop is the one being used to implement an extension method on `IEnumerable\u003cT\u003e` that takes an action with side-effects and executes it against each element.\r\n\r\nThis extension method can be appended to the end of your LINQ operator chain as fluently as any other LINQ operator.\r\n\r\n```C#\r\nvoid Main() {\r\n    var tokens = new[] { \"Hello\", \",\", \" \", \"world\", \"!\" };\r\n    tokens.ForEach(Console.Write);\r\n}\r\n\r\npublic static class EnumerableExtensions {\r\n    public static void ForEach\u003cT\u003e(this IEnumerable\u003cT\u003e source, Action\u003cT\u003e action) {\r\n        foreach (var item in source)\r\n            action(item);\r\n    }\r\n}\r\n```\r\n\r\nDo not use `ForEach()` as a mere replacement for `foreach`! Using `ForEach()` implies that you are mutating state with side-effects, which should happen sparingly. Remember: side-effects are dirty; it's immutability we are after.\r\n\r\n\u003ca name=\"3\"\u003e\u003c/a\u003e\r\n## 3. Map\r\n\r\n`Select()` is your bread...\r\n\r\n```C#\r\nvoid Main() {\r\n    var input = new[] { \"1\", \"2\", \"3\" };\r\n    MapWithLoop(input).Dump();\r\n    MapWithLinq(input).Dump();\r\n}\r\n\r\nList\u003cint\u003e MapWithLoop(string[] input) {\r\n    var result = new List\u003cint\u003e();\r\n\r\n    foreach (var item in input)\r\n        result.Add(int.Parse(item));\r\n\r\n    return result;\r\n}\r\n\r\nIEnumerable\u003cint\u003e MapWithLinq(string[] input) =\u003e input\r\n    .Select(int.Parse);\r\n```\r\n\r\n\u003ca name=\"4\"\u003e\u003c/a\u003e\r\n## 4. Filter\r\n\r\n...and `Where()` is your butter.\r\n\r\n```C#\r\nvoid Main() {\r\n    var input = new[] { 1, 2, 3, 4 };\r\n    FilterWithLoop(input).Dump();\r\n    FilterWithLinq(input).Dump();\r\n}\r\n\r\nList\u003cint\u003e FilterWithLoop(int[] input) {\r\n    var result = new List\u003cint\u003e();\r\n\r\n    foreach (var item in input)\r\n        if (item % 2 == 0)\r\n            result.Add(item);\r\n\r\n    return result;\r\n}\r\n\r\nIEnumerable\u003cint\u003e FilterWithLinq(int[] input) =\u003e input\r\n    .Where(x =\u003e x % 2 == 0);\r\n```\r\n\r\n\u003ca name=\"5\"\u003e\u003c/a\u003e\r\n## 5. Capture index\r\n\r\nSometimes you need the element index in your mapping function. Sounds like the perfect job for a classic `for` loop, you think? Not on my watch!\r\n\r\nLINQ operators usually have overloads that feed the element along with its index to your `Func` if you need them.\r\n\r\nThe example below converts a binary number to decimal. Using the index of the bits this is easy enough, but we have to keep in mind that the least significant bit is the _last_ element of the input. Of course we can work out the correct process with a `for` loop (after fixing this damn off-by-one error...), but see how natural you can express this reversal with LINQ.\r\n\r\n```C#\r\nvoid Main() {\r\n    var input = new[] { 1, 1, 0, 1 };\r\n    CaptureIndexWithLoop(input).Dump();\r\n    CaptureIndexWithLinq(input).Dump();\r\n}\r\n\r\nint CaptureIndexWithLoop(int[] input) {\r\n    var result = 0;\r\n    var lastIndex = input.Length - 1;\r\n\r\n    for (var i = 0; i \u003c input.Length; i++)\r\n        result += MulByNthPowerOfTwo(input[i], lastIndex - i);\r\n\r\n    return result;\r\n}\r\n\r\nint CaptureIndexWithLinq(int[] input) =\u003e input\r\n    .Reverse()\r\n    .Select((x, i) =\u003e MulByNthPowerOfTwo(x, i))\r\n    .Sum();\r\n\r\nint MulByNthPowerOfTwo(int x, int n) =\u003e x \u003c\u003c n;\r\n```\r\n\r\nNotice how the parameters of the lambda in `Select((x, i) =\u003e ...`) match up with parameters of `MulByNthPowerOfTwo(int x, int n)`? This means we could leave them out entirely and write `Select(MulByNthPowerOfTwo)`, which is less redundant.\r\n\r\n\u003ca name=\"6\"\u003e\u003c/a\u003e\r\n## 6. Flatten\r\n\r\nFlattening a list of lists is a fairly common operation. In loop land this is done with nesting. In LINQtopia however, it's just a call to `SelectMany()`.\r\n\r\n```C#\r\nvoid Main() {\r\n    var input = new[] {\r\n        new[] { 1, 2 },\r\n        new[] { 10, 20 },\r\n        new[] { 100, 200 }\r\n    };\r\n\r\n    FlattenWithLoop(input).Dump();\r\n    FlattenWithLinq(input).Dump();\r\n}\r\n\r\nList\u003cint\u003e FlattenWithLoop(int[][] input) {\r\n    var result = new List\u003cint\u003e();\r\n\r\n    foreach (var items in input)\r\n        foreach (var item in items)\r\n            result.Add(item);\r\n\r\n    return result;\r\n}\r\n\r\nIEnumerable\u003cint\u003e FlattenWithLinq(int[][] input) =\u003e input\r\n    .SelectMany(items =\u003e items);\r\n```\r\n\r\nNote how this scales when applied to lists of lists of lists of... With loops you will need even more _nesting_, whereas with LINQ you keep _chaining_ `SelectMany()` until you reach the desired depth.\r\n\r\n\u003ca name=\"7\"\u003e\u003c/a\u003e\r\n## 7. Carry index\r\n\r\nThe previous examples were all fine and dandy, but here is were programmers used to looping begin to object.\r\n\r\nThe example pairs each element index (the place of a runner in a competition) with the elements of a nested collection (the drugs the runner used). We are _carrying_ the indices down into a deeper level, so to speak. It's probably easier to understand if you just run the example right now and have a look at the output.\r\n\r\n\u003e ```\r\n\u003e +---------------------------+\r\n\u003e |Place #2 used amphetamines.|\r\n\u003e +---------------------------+\r\n\u003e |Place #2 used steroids.    |\r\n\u003e +---------------------------+\r\n\u003e |Place #3 used steroids.    |\r\n\u003e +---------------------------+\r\n\u003e\r\n\u003e ...\r\n\u003e ```\r\n\r\n```C#\r\nvoid Main() {\r\n    var input = new[] {\r\n        new TestResult { Runner = \"Road Runner\", Drugs = new string[0] },\r\n        new TestResult { Runner = \"Flash\", Drugs = new[] { \"amphetamines\", \"steroids\" } },\r\n        new TestResult { Runner = \"Sonic\", Drugs = new[] { \"steroids\" } }\r\n    };\r\n\r\n    CarryIndexWithLoop(input).Dump();\r\n    CarryIndexWithLinq(input).Dump();\r\n    CarryIndexWithDirtyLinq(input).Dump();\r\n}\r\n\r\nList\u003cstring\u003e CarryIndexWithLoop(TestResult[] input) {\r\n    var result = new List\u003cstring\u003e();\r\n\r\n    for (var place = 0; place \u003c input.Length; place++)\r\n        foreach (var drug in input[place].Drugs)\r\n            result.Add(MakeReportLine(place, drug));\r\n\r\n    return result;\r\n}\r\n\r\nIEnumerable\u003cstring\u003e CarryIndexWithLinq(TestResult[] input) =\u003e input\r\n    .Select((testResult, place) =\u003e (testResult, place))\r\n    .SelectMany(\r\n        x =\u003e x.testResult.Drugs,\r\n        (x, drug) =\u003e MakeReportLine(x.place, drug));\r\n\r\nIEnumerable\u003cstring\u003e CarryIndexWithDirtyLinq(TestResult[] input) =\u003e input\r\n    .SelectMany((testResult, place) =\u003e testResult\r\n        .Drugs.Select(drug =\u003e MakeReportLine(place, drug)));\r\n\r\nclass TestResult {\r\n    public string Runner { get; set; }\r\n    public string[] Drugs { get; set; }\r\n}\r\n\r\nstring MakeReportLine(int place, string drug) =\u003e $\"Place #{place + 1} used {drug}.\";\r\n```\r\n\r\nCarrying the index from the outer loop into the inner loop's body is straightforward and doesn't look much different than any other nested loop. In LINQ however, this may not come as easy at first, because you explicitely have to \"push\" the index into the next operator.\r\n\r\nBut what seems like a nuisance actually makes your life easier. Implicitely being able to use variables from outer scopes also increases the amount of information readers of your code have to keep track of. By explicitely declaring what goes into the next operator, you are not only announcing what the important parts of the input data are. You are also keeping the scope of the following operator clean and tidy, making it easier to reason about.\r\n\r\nAlso consider how both solutions scale if we for instance wanted to only list the drugs of the first and second places in our report: in the loop solution we'd introduce another level of nesting (`if (place \u003e 3)`) inbetween the two loops; in the LINQ solution we'd add a filter (`Where(x =\u003e x.place \u003e 3)`) to our chain without any increase in the nesting level.\r\n\r\nDon't try and boost your operator's scope by nesting lambdas like `CarryIndexWithDirtyLinq()` does. This is basically the same way the looping solution works and we want to do better than that.\r\n\r\nOf course, we are not restricted to carrying only indices. The same patterns works when you have to carry other data from the top level collection into the deeper levels of nested collections.\r\n\r\n\u003ca name=\"8\"\u003e\u003c/a\u003e\r\n## 8. Cross join\r\n\r\nWhen you see nested loops working away on two collections, chances are they are both being joined somehow. The question is: what kind of join are you looking at?\r\n\r\nWhen done with loops it's not obvious. Often a develooper (sorry) will \"invent\" his or her own logic that looks like someone was trying to build the death star. This kind of DIY joining does nothing but obfuscate intent and makes your reviewer question your mental stability.\r\n\r\nAlways figure out what kind of join your code should perform _before_ you start coding. Then express this operation in abstract terms (cross, inner, left, right, full). Those are concepts that your readers can google if they don't know them.\r\n\r\nThe simplest kind of join is the cross join (a.k.a. cartesian product): everything is paired with everthing. In LINQ it's just a call to our old friend `SelectMany()`.\r\n\r\n```C#\r\nvoid Main() {\r\n    var input1 = new[] { 'A', 'B', 'C' };\r\n    var input2 = new[] { 1, 2, 3 };\r\n    CrossJoinWithLoop(input1, input2).Dump();\r\n    CrossJoinWithLinq(input1, input2).Dump();\r\n}\r\n\r\nList\u003cstring\u003e CrossJoinWithLoop(char[] input1, int[] input2) {\r\n    var result = new List\u003cstring\u003e();\r\n\r\n    foreach (var item1 in input1)\r\n        foreach (var item2 in input2)\r\n            result.Add($\"{item1}{item2}\");\r\n\r\n    return result;\r\n}\r\n\r\nIEnumerable\u003cstring\u003e CrossJoinWithLinq(char[] input1, int[] input2) =\u003e input1\r\n    .SelectMany(_ =\u003e input2, (x1, x2) =\u003e $\"{x1}{x2}\");\r\n```\r\n\r\n\u003ca name=\"9\"\u003e\u003c/a\u003e\r\n## 9. Inner join\r\n\r\nThe inner join also is a fairly simple join. It's basically the intersection of two lists based on a common key.\r\n\r\nThe inner join is so prevalent that LINQ has it's own operator for it: `Join()`. There is no excuse not to use it. Especially considering that the inner join is often implemented rather ineffeciently when done by looplanders. Can you spot the performance issue in the loop example?\r\n\r\nThe nested loops look innocent, but we do a linear search there in order to find the matching item in `input2`. Madness! That's `O(NM)`! Converting `input2` to a `Lookup\u003cBar\u003e` beforehand would give us `O(N)`.\r\n\r\nOr we can just use `Join()` and let LINQ do the busywork for us...\r\n\r\n```C#\r\nvoid Main() {\r\n    var input1 = new List\u003cFoo\u003e {\r\n        new Foo(1, \"A\"), new Foo(2, \"B\"), new Foo(3, \"C\")\r\n    };\r\n    var input2 = new List\u003cBar\u003e {\r\n        new Bar(1, 100), new Bar(2, 200), new Bar(2, 201), new Bar(4, 400)\r\n    };\r\n    InnerJoinWithLoop(input1, input2).Dump();\r\n    InnerJoinWithLinq(input1, input2).Dump();\r\n}\r\n\r\nList\u003cstring\u003e InnerJoinWithLoop(List\u003cFoo\u003e input1, List\u003cBar\u003e input2) {\r\n    var result = new List\u003cstring\u003e();\r\n\r\n    foreach (var item1 in input1)\r\n        foreach (var item2 in input2.FindAll(x =\u003e x.Id == item1.Id))\r\n            result.Add($\"{item1.Value}{item2.Value}\");\r\n\r\n    return result;\r\n}\r\n\r\nIEnumerable\u003cstring\u003e InnerJoinWithLinq(List\u003cFoo\u003e input1, List\u003cBar\u003e input2) =\u003e input1\r\n    .Join(\r\n        input2,\r\n        f =\u003e f.Id,\r\n        b =\u003e b.Id,\r\n        (f, b) =\u003e $\"{f.Value}{b.Value}\");\r\n\r\nclass Foo {\r\n    public Foo(int id, string v) { Id = id; Value = v; }\r\n    public int Id { get; set; }\r\n    public string Value { get; set; }\r\n}\r\n\r\nclass Bar {\r\n    public Bar(int id, int v) { Id = id; Value = v; }\r\n    public int Id { get; set; }\r\n    public int Value { get; set; }\r\n}\r\n```\r\n\r\n\u003ca name=\"10\"\u003e\u003c/a\u003e\r\n## 10. Left join\r\n\r\nThe left join is a bit more tricky to do with LINQ, because there is no inherent support for it.\r\n\r\nLuckily we can use `GroupJoin()` to get an empty list for all the items from the left side that were not present in the right side. Calling `DefaultIfEmpty()` on the joined elements will return the joined items or the singleton list created from `default(Bar)` in case it was empty.\r\n\r\n```C#\r\nvoid Main() {\r\n    var input1 = new List\u003cFoo\u003e {\r\n        new Foo(1, \"A\"), new Foo(2, \"B\"), new Foo(3, \"C\")\r\n    };\r\n    var input2 = new List\u003cBar\u003e {\r\n        new Bar(2, 200), new Bar(3, 300), new Bar(4, 400)\r\n    };\r\n    LeftJoinWithLoop(input1, input2).Dump();\r\n    LeftJoinWithLinq(input1, input2).Dump();\r\n}\r\n\r\nList\u003cstring\u003e LeftJoinWithLoop(List\u003cFoo\u003e input1, List\u003cBar\u003e input2) {\r\n    var result = new List\u003cstring\u003e();\r\n\r\n    foreach (var item1 in input1) {\r\n        var item2 = input2.Find(x =\u003e x.Id == item1.Id);\r\n        result.Add($\"{item1.Value}{item2?.Value}\");\r\n    }\r\n\r\n    return result;\r\n}\r\n\r\nIEnumerable\u003cstring\u003e LeftJoinWithLinq(List\u003cFoo\u003e input1, List\u003cBar\u003e input2) =\u003e input1\r\n    .GroupJoin(\r\n        input2,\r\n        f =\u003e f.Id,\r\n        b =\u003e b.Id,\r\n        (f, bs) =\u003e (f, bs))\r\n    .SelectMany(\r\n        x =\u003e x.bs.DefaultIfEmpty(),\r\n        (x, b) =\u003e $\"{x.f.Value}{b?.Value}\");\r\n```\r\n\r\nThe LINQ solution looks a bit hacky but should be extracted into a reusable extension method anyway. The [MoreLINQ](https://github.com/morelinq/MoreLINQ) nuget package offers a `LeftJoin()` as well.\r\n\r\n\u003ca name=\"11\"\u003e\u003c/a\u003e\r\n## 11. Right join\r\n\r\nThe right join works the same way as the left join only with the operands flipped.\r\n\r\n```C#\r\nvoid Main() {\r\n    var input1 = new List\u003cFoo\u003e {\r\n        new Foo(1, \"A\"), new Foo(2, \"B\"), new Foo(3, \"C\")\r\n    };\r\n    var input2 = new List\u003cBar\u003e {\r\n        new Bar(2, 200), new Bar(3, 300), new Bar(4, 400)\r\n    };\r\n    RightJoinWithLoop(input1, input2).Dump();\r\n    RightJoinWithLinq(input1, input2).Dump();\r\n}\r\n\r\nList\u003cstring\u003e RightJoinWithLoop(List\u003cFoo\u003e input1, List\u003cBar\u003e input2) {\r\n    var result = new List\u003cstring\u003e();\r\n\r\n    foreach (var item2 in input2) {\r\n        var item1 = input1.Find(x =\u003e x.Id == item2.Id);\r\n        result.Add($\"{item1?.Value}{item2.Value}\");\r\n    }\r\n\r\n    return result;\r\n}\r\n\r\nIEnumerable\u003cstring\u003e RightJoinWithLinq(List\u003cFoo\u003e input1, List\u003cBar\u003e input2) =\u003e input2\r\n    .GroupJoin(\r\n        input1,\r\n        b =\u003e b.Id,\r\n        f =\u003e f.Id,\r\n        (b, fs) =\u003e (b, fs))\r\n    .SelectMany(\r\n        x =\u003e x.fs.DefaultIfEmpty(),\r\n        (x, f) =\u003e $\"{f?.Value}{x.b.Value}\");\r\n```\r\n\r\nThe LINQ implementation should also be generalized to work on any `IEnumerable\u003cTLeft\u003e` and `IEnumerable\u003cTRight\u003e` though. Or you could use the `RightJoin()` from MoreLINQ.\r\n\r\n\u003ca name=\"12\"\u003e\u003c/a\u003e\r\n## 12. Full join\r\n\r\nThe full join is a beast. Period. Prepare for some serious head-scratching if you are ever faced with implementing it.\r\n\r\nThe naïve solution involves doing both the left and right join and combining the results while duplicates are being discarded. This is how the loop solution works.\r\n\r\nThe LINQ solution (stolen from https://stackoverflow.com/a/13503860/1025555) is a bit more clever. It creates `Lookup`s first which offer constant-time lookup speed of the ids. It's still not exactly beautiful and should be hidden by generalizing it. MoreLINQ has a `FullJoin()` as well.\r\n\r\n```C#\r\nvoid Main() {\r\n    var input1 = new List\u003cFoo\u003e {\r\n        new Foo(1, \"A\"), new Foo(2, \"B\"), new Foo(3, \"C\")\r\n    };\r\n    var input2 = new List\u003cBar\u003e {\r\n        new Bar(2, 200), new Bar(3, 300), new Bar(4, 400)\r\n    };\r\n    FullJoinWithLoop(input1, input2).Dump();\r\n    FullJoinWithLinq(input1, input2).Dump();\r\n}\r\n\r\nList\u003cstring\u003e FullJoinWithLoop(List\u003cFoo\u003e input1, List\u003cBar\u003e input2) {\r\n    var result = new HashSet\u003cstring\u003e();\r\n\r\n    foreach (var item1 in input1) {\r\n        var item2 = input2.Find(x =\u003e x.Id == item1.Id);\r\n        result.Add($\"{item1.Value}{item2?.Value}\");\r\n    }\r\n\r\n    foreach (var item2 in input2) {\r\n        var item1 = input1.Find(x =\u003e x.Id == item2.Id);\r\n        result.Add($\"{item1?.Value}{item2.Value}\");\r\n    }\r\n\r\n    return result.ToList();\r\n}\r\n\r\nIEnumerable\u003cstring\u003e FullJoinWithLinq(List\u003cFoo\u003e input1, List\u003cBar\u003e input2) {\r\n    var lookup1 = input1.ToLookup(x =\u003e x.Id);\r\n    var lookup2 = input2.ToLookup(x =\u003e x.Id);\r\n    var ids = new HashSet\u003cint\u003e(lookup1.Select(p =\u003e p.Key));\r\n    ids.UnionWith(lookup2.Select(p =\u003e p.Key));\r\n\r\n    return ids\r\n        .SelectMany(id =\u003e lookup1[id].DefaultIfEmpty(), (id, f) =\u003e (id, f))\r\n        .SelectMany(x =\u003e lookup2[x.id].DefaultIfEmpty(), (x, b) =\u003e (x.f, b))\r\n        .Select(x =\u003e $\"{x.f?.Value}{x.b?.Value}\");\r\n}\r\n```\r\n\r\n\u003ca name=\"13\"\u003e\u003c/a\u003e\r\n## 13. Conditional join\r\n\r\nSo why did I spent so much time showing how to do the different kinds of joins with LINQ? The answer is simple: because that's what developers are doing more often than they might realize.\r\n\r\nThe problem is, in the real world requirements are not as \"synthetic\" as in the previous examples. Reality is often complex and it's hard to identify and abstract patterns when they are riddled with special cases and exceptions.\r\n\r\nFor instance, no client will come to you and request a plain inner join of `Foo`s and `Bar`s. It's more likely that he or she says you should \"marry\" every second `Foo` with all the `Bar`s that \"belong to it\". _But_ only the `Bar`s that are whole numbers!\r\n\r\n\"Easy enough\", might Sir Loopalot think and begin hacking a nested loop solution right away. And after sprinkling a couple of `if`s here and there it even works! Done.\r\n\r\nI say take a step back first. Think about the abstract nature of the problem. What kind of patterns are beneath the requirements? Can I split the process into several independent steps? What parts of my toolbox can I reuse?\r\n\r\nOf course, most developers will consider those questions before starting to code. But LINQ actually enforces this kind of thinking. LINQ makes it harder for you to take shortcuts that sacrifice maintainability on the long run.\r\n\r\n```C#\r\nvoid Main() {\r\n    var input1 = new List\u003cFoo\u003e {\r\n        new Foo(1, \"A\"), new Foo(2, \"B\"), new Foo(3, \"C\"), new Foo(4, \"D\")\r\n    };\r\n    var input2 = new List\u003cBar\u003e {\r\n        new Bar(1, 10), new Bar(2, 20.3), new Bar(3, 30.5), new Bar(4, 40)\r\n    };\r\n    ConditionalJoinWithLoop(input1, input2).Dump();\r\n    ConditionalJoinWithLinq(input1, input2).Dump();\r\n}\r\n\r\nList\u003cstring\u003e ConditionalJoinWithLoop(List\u003cFoo\u003e input1, List\u003cBar\u003e input2) {\r\n    var result = new List\u003cstring\u003e();\r\n\r\n    foreach (var item1 in input1) {\r\n        if (item1.Id % 2 != 0)\r\n            continue;\r\n\r\n        foreach (var item2 in input2.FindAll(x =\u003e x.Id == item1.Id))\r\n            if (item2.Value == Math.Floor(item2.Value))\r\n                result.Add($\"{item1.Value}{item2.Value}\");\r\n    }\r\n\r\n    return result;\r\n}\r\n\r\nIEnumerable\u003cstring\u003e ConditionalJoinWithLinq(List\u003cFoo\u003e input1, List\u003cBar\u003e input2) =\u003e input1\r\n    .Where(f =\u003e f.Id % 2 == 0)\r\n    .Join(input2, f =\u003e f.Id, b =\u003e b.Id, (f, b) =\u003e (f, b))\r\n    .Where(x =\u003e x.b.Value == Math.Floor(x.b.Value))\r\n    .Select(x =\u003e $\"{x.f.Value}{x.b.Value}\");\r\n```\r\n\r\nThe amount and the density of lambdas in the LINQ solution might be confusing at first, but that's just someting one has to learn and get used to. Look at it this way: understanding how some method solves a specific problem with nested `for`s and `if`s helps you with exactly one thing--understanding how this one method works. But learning `Join()` and how it can be used will help you everytime you see it in action somewhere else.\r\n\r\nNethertheless I will show how to beautify the above solution in [example 17](#17).\r\n\r\n\u003ca name=\"14\"\u003e\u003c/a\u003e\r\n## 14. Reduce\r\n\r\nNow we get to the mother of all LINQ operators. Quite Literally, because this operator could in fact be used to implement most of the operators I've shown you know so far.\r\n\r\nEnter `Aggregate()`. Probably the most dreaded LINQ operator there is, since it seems unwieldy and hard to wrap your head around. I know I had my problems at first.\r\n\r\nBut it really boils down to this: `Aggreagte()` reduces a list of things to a single thing with the operation you provide it. If the list might be empty or the type of the result is different from the type of the elements in the list, then you also have to give it a starting value (the _seed_).\r\n\r\n```C#\r\nvoid Main() {\r\n    var input = new[] { 1, 2, 3 };\r\n    ReduceWithLoop(input).Dump();\r\n    ReduceWithLinq(input).Dump();\r\n}\r\n\r\nint ReduceWithLoop(int[] input) {\r\n    var result = 0;\r\n\r\n    foreach (var item in input)\r\n        result += item;\r\n\r\n    return result;\r\n}\r\n\r\nint ReduceWithLinq(int[] input) =\u003e input\r\n    .Aggregate(0, (sum, next) =\u003e sum + next);\r\n```\r\n\r\n\u003ca name=\"15\"\u003e\u003c/a\u003e\r\n## 15. Moving window\r\n\r\nWhat if operating on list elements independently is not sufficient? What if the element's neighbors need to be considered as well?\r\n\r\nTruth is there is no nice way of doing this with LINQ. If you know you are dealing with `IList\u003cT\u003e`s then you can use the trick demonstrated below. But if your `IEnumerable\u003cT\u003e` does not have an indexer then `ElementAt()` will go through all the elements until it reaches the `i`th one and performance will suffer.\r\n\r\nAlternatively you can use MoreLINQ's `Pairwise()`. I added it's implementation here so you can see how it looks when LINQ reaches its limits.\r\n\r\nIf you have to resort to using the `IEnumerator\u003cT\u003e` of your `IEnumerable\u003cT\u003e` it's probably best that you generalize the code into a reusable extension method and hide it somewhere.\r\n\r\n```C#\r\nvoid Main() {\r\n    var input = new[] { 1, 2, 3, 2, 1 };\r\n    MovingWindowWithLoop(input).Dump();\r\n    MovingWindowWithLinq1(input).Dump();\r\n    MovingWindowWithLinq2(input).Dump();\r\n}\r\n\r\nList\u003cint\u003e MovingWindowWithLoop(int[] input) {\r\n    var result = new List\u003cint\u003e();\r\n\r\n    for (int i = 1; i \u003c input.Length; i++) {\r\n        result.Add(input[i] - input[i - 1]);\r\n    }\r\n\r\n    return result;\r\n}\r\n\r\nIEnumerable\u003cint\u003e MovingWindowWithLinq1(int[] input) =\u003e input\r\n    .Skip(1)\r\n    .Select((x, i) =\u003e x - input.ElementAt(i));\r\n\r\nIEnumerable\u003cint\u003e MovingWindowWithLinq2(int[] input) =\u003e input\r\n    .Pairwise((x1, x2) =\u003e x2 - x1);\r\n\r\npublic static class EnumerableExtensions {\r\n    public static IEnumerable\u003cTResult\u003e Pairwise\u003cTSource, TResult\u003e(\r\n        this IEnumerable\u003cTSource\u003e source,\r\n        Func\u003cTSource, TSource, TResult\u003e resultSelector) {\r\n\r\n        using (var e = source.GetEnumerator()) {\r\n            if (!e.MoveNext())\r\n                yield break;\r\n\r\n            var previous = e.Current;\r\n            while (e.MoveNext()) {\r\n                yield return resultSelector(previous, e.Current);\r\n                previous = e.Current;\r\n            }\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n\u003ca name=\"16\"\u003e\u003c/a\u003e\r\n## 16. Tap\r\n\r\nThe tap operator is helpful when you have to hook side effects lazily into your operator chain.\r\n\r\nThink of it as tapping a phone line: you can listen in on all elements going down the chain without anyone below noticing.\r\n\r\n```C#\r\nvoid Main() {\r\n    new[] {1, 2, 3}\r\n        .Select(x =\u003e x + 1)\r\n        .Tap(Console.Write)\r\n        .Select(x =\u003e x * x)\r\n        .Dump();\r\n}\r\n\r\npublic static class EnumerableExtensions {\r\n    public static IEnumerable\u003cT\u003e Tap\u003cT\u003e(\r\n        this IEnumerable\u003cT\u003e source, Action\u003cT\u003e action)\r\n        =\u003e source.Select(x =\u003e { action(x); return x; });\r\n}\r\n```\r\n\r\n\u003ca name=\"17\"\u003e\u003c/a\u003e\r\n## 17. Modularization\r\n\r\nWhat makes LINQ so versatile are the ways a LINQ chain can be modularized.\r\n\r\nFor one, you can replace complicated predicates and lambdas with private methods. You can also do that with the expression in an `if` statement, but the fact that LINQ operators take `Func`s allows you to elide those noisy arguments.\r\n\r\nIt's also possible to replace one or more operators of your chain with an extension method that reduces complexity and allows readers to focus on the important parts. Notice how the second LINQ example almost reads like what the client was requesting back in [example 13](#13).\r\n\r\n```C#\r\nvoid Main() {\r\n    var input1 = new List\u003cFoo\u003e {\r\n        new Foo(1, \"A\"), new Foo(2, \"B\"), new Foo(3, \"C\"), new Foo(4, \"D\")\r\n    };\r\n    var input2 = new List\u003cBar\u003e {\r\n        new Bar(1, 10), new Bar(2, 20.3), new Bar(3, 30.5), new Bar(4, 40)\r\n    };\r\n    ConditionalJoinWithLinq(input1, input2).Dump();\r\n    ConditionalJoinWithBeautifulLinq(input1, input2).Dump();\r\n}\r\n\r\nIEnumerable\u003cstring\u003e ConditionalJoinWithLinq(\r\n    List\u003cFoo\u003e input1, List\u003cBar\u003e input2)\r\n    =\u003e input1\r\n        .Where(f =\u003e f.Id % 2 == 0)\r\n        .Join(input2, f =\u003e f.Id, b =\u003e b.Id, (f, b) =\u003e (f, b))\r\n        .Where(x =\u003e x.b.Value == Math.Floor(x.b.Value))\r\n        .Select(x =\u003e $\"{x.f.Value}{x.b.Value}\");\r\n\r\nIEnumerable\u003cstring\u003e ConditionalJoinWithBeautifulLinq(\r\n    List\u003cFoo\u003e input1, List\u003cBar\u003e input2)\r\n    =\u003e input1\r\n        .EverySecondFoo()\r\n        .JoinBars(input2.Where(IsInteger))\r\n        .Select(x =\u003e $\"{x.f.Value}{x.b.Value}\");\r\n\r\ninternal static class Extensions {\r\n    public static IEnumerable\u003cFoo\u003e EverySecondFoo(this IEnumerable\u003cFoo\u003e foos)\r\n        =\u003e foos.Where(f =\u003e f.Id % 2 == 0);\r\n\r\n    public static IEnumerable\u003c(Foo f, Bar b)\u003e JoinBars(\r\n        this IEnumerable\u003cFoo\u003e foos, IEnumerable\u003cBar\u003e bars)\r\n        =\u003e foos.Join(bars, f =\u003e f.Id, b =\u003e b.Id, (f, b) =\u003e (f, b));\r\n}\r\n\r\nbool IsInteger(Bar b) =\u003e b.Value == Math.Floor(b.Value);\r\n```\r\n\r\nNotice how we are using extensions methods in way that probably wasn't intended by the C# developers. Extensions methods have to be public, but here we only need them locally in our current context. The best we can do is to hide them in a deep namespace and make the extension class `internal`.\r\n\r\n\u003ca name=\"18\"\u003e\u003c/a\u003e\r\n## 18. Laziness\r\n\r\nWhat throws a lot of people off when starting with LINQ is it's laziness. Take the LINQ chain below for example. It increments each number in the array by one, squares them, and then prints them to the console using `Tap()`. But when you run the example in LINQPad nothing happens. There is no output whatsoever. Why is that?\r\n\r\n```C#\r\nvoid Main() {\r\n    new[] { 1, 2, 3 }\r\n        .Select(x =\u003e x + 1)\r\n        .Select(x =\u003e x * x)\r\n        .Tap(Console.WriteLine);\r\n}\r\n\r\npublic static class Extensions {\r\n    public static IEnumerable\u003cT\u003e Tap\u003cT\u003e(\r\n        this IEnumerable\u003cT\u003e source, Action\u003cT\u003e action)\r\n        =\u003e source.Select(x =\u003e { action(x); return x; });\r\n}\r\n```\r\n\r\nThe answer is that LINQ operators only begin their work when something actually requests a value from them. This is called _deferred execution_: you can chain as many LINQ operators as you like and even pass that query around in a variable, but as long as no one does \"something\" with its values, nothing will be executed.\r\n\r\nThat \"something\" that has to be done is _enumeration_ and is usually initiated by a `foreach` loop iterating over the `IEnumerable` returned by a LINQ chain. Of course it doesn't have to be an explicit `for`/`foreach` loop. Some LINQ operators, like `ToList()` and our `ForEach()`, do the looping implicitly.\r\n\r\nEnumerating the values of a LINQ chain triggers a process called _materialization_ where the last operator of the chain is queried for a value by the enumerating loop. This operator will in turn request a value from its input, which might be another operator preceeding it. The chain is traversed backwards up until an `IEnumerable` is reached that acts as the _value generator_ of the chain. Such a generator could be anything from an in-memory collection, a specialized generator function like `Enumerable.Range()`, the filesystem, a database query; you name it.\r\n\r\nAs soon as the first value was generated it will be fed to the first operator in the chain, which will do its work and then forward it to the next operator. This continues until the value reaches the _enumerator_. When the enumerator requests the next value the same process will start over again.\r\n\r\nIf you check the implementation of `Tap()` now, you will notice that it does not do any looping at all. It just adds another `Select()` to the chain. That's why there is nothing happening. We cannot use `Tap()` to terminate our chain, because it cannot act as an enumerator. We have to use `ForEach()`.\r\n\r\nThe rule of thumb is that an operator returning `IEnumerable\u003cT\u003e` will deferr execution and an operator returning `void` will enumerate.\r\n\r\nLet's finish this section with a motivational picture that should help getting those lazy LINQ operators to work harder:\r\n\r\n\u003cp align=\"center\"\u003e\r\n  \u003cimg src=\"https://user-images.githubusercontent.com/1454629/53301663-e3660380-3855-11e9-8316-58d447d7b33e.png\"/\u003e\r\n\u003c/p\u003e\r\n\r\n\u003ca name=\"19\"\u003e\u003c/a\u003e\r\n## 19. How to be lazy\r\n\r\nYou might wonder now how an `IEnumerable` achieves this laziness. How can `Enumerable.Range()`, for instance, feed one value downstream and stop executing until the next value is requested? How does it know where it left off earlier when the request for the next value comes in?\r\n\r\nOn the syntax level this is done with the `yield` keyword. It can only be used inside methods or properties that return an `IEnumerable`. When a value is requested from this member, its code will be run until the first `yield return \u003csome value\u003e` is hit. The value will be returned, the state of all local variables will be saved, and execution stops until the next value is requested. As soon as this happens, the local state will be restored and execution will be picked up after the `yield return` that was executed last.\r\n\r\nStill sounds like magic? Maybe so, but at least we now know how to write lazy methods ourselves. We will later have a brief look at how C# actually implements this.\r\n\r\nLet's start by looking at the `Range()` method below. For simplicity we iterate over the result of `Range()` with regular `foreach` loop. I added some debug outputs so its easier to see which statement gets executed when. Notice how execution goes back and forth between the enumerator loop and the generator method. Also notice how such asynchronous producer/consumer scenarios usually had to be implemented with painful multi-threading and now all we need is this little word `yield` and an `IEnumerable` return type!\r\n\r\n```C#\r\nvoid Main() {\r\n    foreach (var num in Range(10, 13))\r\n        Console.WriteLine($\"got {num}\");\r\n}\r\n\r\npublic IEnumerable\u003cint\u003e Range(int start, int end) {\r\n    for (var n = start; n \u003c= end; n++) {\r\n        Console.WriteLine($\"yielding {n}\");\r\n        yield return n;\r\n        Console.WriteLine(\"picking up work again...\");\r\n    }\r\n}\r\n```\r\n\r\nNow for something more glorious: the Fibonacci sequence! There is a minor problem though: the first two Fibonacci numbers are predefined and not part of our computation loop. How do we deal with this? Simple! Just yield them before the computation loop. Those two `yield return` statements will never be hit again, because exectution will always continue _after_ the `yield return` executed last.\r\n\r\nAnd yet another another problem: the Fibonacci sequence is infinite, but the range of `int` is not. And the numbers grow so fast that we cannot even get 50 values from our generator without getting overflown negative numbers. The solution is to only return those numbers we can accurately compute and stop the generator as soon as we overflow the `int` range. We do this by breaking the generator loop almost like we would break a regular loop. We only have to use `yield break` instead of `break`, because we are in an `IEnumerable` context.\r\n\r\n```C#\r\npublic IEnumerable\u003cint\u003e FibonacciNumbers {\r\n    get {\r\n        var preprevious = 0;\r\n        var previous = 1;\r\n        yield return preprevious;\r\n        yield return previous;\r\n\r\n        while (true) {\r\n            var current = preprevious + previous;\r\n\r\n            if (current \u003c 0) yield break;\r\n\r\n            yield return current;\r\n            preprevious = previous;\r\n            previous = current;\r\n        }\r\n    }\r\n}\r\n```\r\n\r\nYou might want to get fancy and define your collections with static data the same way the property `MyHobbies` does it. Although it looks a tiny bit cleaner compared to `new`ing an array, you still shouldn't do this. The reason is that the compiler generates a whole lot of code for each member that uses `yield`. Every such member essentially becomes its own class capable of halting and resuming execution while preserving local state (i.e. a state machine). You can see for yourself by opening a decompiled view of our LINQPad code with `Alt`+`Shift`+`R` (requires [ILSpy](https://github.com/icsharpcode/ILSpy/releases)) and navigating to the compiler-generated class `\u003cget_MyHobbies\u003ed__5`.\r\n\r\n```C#\r\npublic IEnumerable\u003cstring\u003e MyHobbies {\r\n    get {\r\n        yield return \"horses\";\r\n        yield return \"flowers\";\r\n        yield return \"guns\";\r\n    }\r\n}\r\n```\r\n\r\n\u003ca name=\"20\"\u003e\u003c/a\u003e\r\n## 20. Debugging\r\n\r\nSooner or later after you started writing more LINQ chains you will have to debug them now and then. You will notice that things work a bit different than you are used to from regular imperative code with loops.\r\n\r\nYou cannot set a break point on an operator and check how the input data as a whole has been transformed so far. All you can do is break inside an operator's lambda and check each item individually. So you will probably start splitting the LINQ chain into smaller chains writing to temporary lists in order to inspect the data there. This ends up in a tedious back and forth between changing the code and running the debugger until you find the bug. And after that you will have to change it all back again!\r\n\r\nI claim that it's not LINQ that is at fault here; it's your habits. Think about it: all a LINQ chain does is combine well-defined operators which are in itself almost quaranteed to work correctly. Typical off-by-one index errors cannot happen, because we don't maintain the indices ourselves. State variables shouldn't be subject to unexpected changes, because ideally our LINQ chains work in an immutable fashion (i.e. they do not change items, but yield new ones instead).\r\n\r\nThe bug will most likely be in our assumptions. Is the input data really shaped the way we think it is? Did we understand all the operators correctly? If we wrote a custom operator for our chain: is it covered by unit tests?\r\n\r\nIn my experience you will spend a lot less time stepping through the code with a debbugger (which should always be a last resort anyway). Instead you will spend more time just looking at your chain and checking whether the operators are combined correctly.\r\n\r\nThat being said: if you really have the need to see the complete data inside the `Enumerable` after an arbitrary step in you chain, you can use the `TapAll()` operator defined below. It's tap action does not take an individual item, but all items instead. Use it with a dummy action where you can place a break point (e.g. `TapAll(x =\u003e {; })`) to inspect the data inside the debug watch.\r\n\r\n```C#\r\nvoid Main() {\r\n    new[] { 1, 2, 3 }\r\n        .TapAll(x =\u003e {})\r\n        .Select(x =\u003e x + 1)\r\n        .TapAll(x =\u003e {})\r\n        .Select(x =\u003e x * x)\r\n        .TapAll(x =\u003e {})\r\n        .Dump();\r\n}\r\n\r\npublic static class Extensions {\r\n    public static IEnumerable\u003cT\u003e TapAll\u003cT\u003e(\r\n        this IEnumerable\u003cT\u003e source, Action\u003cIEnumerable\u003cT\u003e\u003e action) {\r\n        action(source);\r\n        return source;\r\n    }\r\n}\r\n```\r\n\r\n\u003ca name=\"summary\"\u003e\u003c/a\u003e\r\n## 21. Summary\r\n\r\nLet's summarize the advantages of LINQ we noticed in the previous examples.\r\n\r\n- LINQ promotes reuse whereas code with loops tends to reinvent the wheel. If the compiler can do the looping for me then why should I bother to do it myself?\r\n- The different LINQ operators have to be learned once, but every instance of a loop has to be understood on its own, because its effect is not always obvious and might even depend on minor implemenation details.\r\n- LINQ enforces a recognizable pattern: split your problem into smaller operations and chain them to form a pipeline.\r\n- LINQ is _declarative_ by nature and allows you to focus on intent instead of implemenation.\r\n- The more complex a problem the more nesting will be required in a solution using loops. LINQ chains are usually not nested, but linear and can be extended easily.\r\n- LINQ chains can be seamlessly modularized using extension functions.\r\n- Some list operations (e.g. join) have performance traps when DIY'ed naïvely.\r\n- `Func`s allow you to omit noisy parameters and arguments.\r\n\r\nI hope that I could demonstrate how cleanly even complex logic can be organized with LINQ. I also hope you now share some of my obsessive hatred of loops :)\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbert2%2Flove-linq-hate-loops","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbert2%2Flove-linq-hate-loops","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbert2%2Flove-linq-hate-loops/lists"}