LINQ explained with sketches


The eBook

The official eBook is here. All what you see below and more are part of my free eBook "LINQ explained with sketches". You can grab the free copy here:

This blog post will show a lot of LINQ functions broken down in smaller parts. It will always starts with a info graphic which then gets explained later.

Part 1


With select we create a projection from one item to another. Simply speaking we map from our a given type to a desired type. The result set has the same amount of items as the source set.


Where filters based on true/false conditions. In the given example we only want to have green circles. The result set can be the same, less or even empty.


SelectMany is used to flatten lists. If you have a list inside a list we can use it to flatten this into a one dimensional representation.


With Zip we "merge" two lists by a given merge function. We merge objects together until we run out of objects on either of the lanes. As seen in the example: The first lane has 2 elements, the second has 3. Therefore the result set contains only 2 elements.


OrderBy orders the list given by your comparison function or by an intrinsic function (e.g. .NET knows how to sort numbers). OrderBy as well as OrderByDescending are stable. The result set has the same amount of items as the source set.


Distinct returns a new enumerable where all duplicates are removed, kind of like a Set. Be careful that for reference type the default is to check for equality of references, which can lead to false results. The result set can be the same or smaller.

Part 2


Also known as "reduce". The main idea is to aggregate/reduce a set of inputs into one single value. A sum of list would be an example of aggregate. Also defining the average / max / min would be prominent examples. It always starts with start value and every single element in the list gets aggregated by the user given function on top.


This nice helper, introduced with .NET 6, creates smaller sub-lists from a given list. Just imagine as creating smaller batches from a list.


The union of two lists will result in every distinct element which is in both of your lists. It behaves like a set, so duplicated items are removed. Just imagine you have both lists together and call Distinct.


Works similiar to Union but now we check which elements are present in list A AND list B. Only elements present in both will be in the result set. Also here: Only unique items are in the new list. Duplicates are automatically removed.


Any checks if at least one element satisfies your condition. If so, it returns true. If there is no element that meets the condition, then it returns false. Any also immediately stops processing once it founds one element.


As the name implies checks if ALL of your elements in the list satisfy a certain condition. If so returns true, otherwise false. If ALL finds an element which does not satisfy the condition it immediately stops processing and returns false.


Append puts the given element at the end of the list.


Puts the given element at the beginning of the list.

Part 3


With MaxBy as well as MinBy we can also make a projection to a property of our class and get the object where this exact property is the "biggest".


DistinctBy works similar to Distinct but instead of the level of the object itself we can define a projection to a property where we want to have a distinct result set.


This methods creates a lookup. A lookup is defined that we have a key which can point to list of objects (1 to n relation). The first argument takes the "key"-selector. The second selector is the "value". This can be the object itself or a property of the object itself. At the end we have a list of distinct keys where the values share that exact key. A LookUp-object is immutable. You can't add elements afterwards.


ToDictionary works similar to ToLookup with a key difference. The ToDictionary method only allows 1 to 1 relations. If two items share the same key, it will result in an exception that the key is already present. Also the dictionary can be mutated afterwards (for example with the Add method).


Join works similar to a SQL Left-Join. We have two sets we want to join. The next two arguments are the "key" selectors of each list. What Join basically does is it takes every element in list A and compares it with the given "key-selector" against the key-selector of list b. If it matches we can create a new object C, which can consist out of those two elements.

Part 4


Take allows us to "take" the given amount of elements. If we have less elements in the array than we want to take, then Take() will only return the remaining objects.


With Skip we "skip" the given amount of elements. If we skip more elements than our list holds, we get an empty enumeration back. Take and Skip together can be very powerful for stuff like pagination.


OfType checks every element in the enumeration if it is of a given type (also inherited types count as that given type) and returns them in a new enumeration. That helps especially if we have untyped arrays (object) or we want a special subclass of the given enumeration.


GroupBy groups the enumeration by a given projection / key. All elements which share this exact key get grouped together. It is almost identical to "ToLookup" with a very big difference. GroupBy means "I am building an object to represent the question 'what would these things look like if I organised them by group?'" Calling ToLookup means "I want a cache of the entire thing right now organised by group."


Returns a reversed version of the given enumeration.


First returns the first occurrence of an enumeration. Even if there are elements later it always returns immediately after the first found item. If no element is found, it throws an exception.


Single does not return immediately after the first occurrence. The difference to first is that Single ensures there is not a second item of the given type / predicate. Therefore Single has to go through the whole enumeration (worst case) if it can find another item. If so, it throws an exception. If no element is found, it throws an exception.



If no element is found in the given enumeration it returns it default (for reference types null and for value types the given default). Since .NET6 we can pass in what "default" means to us. Therefore we can have non-nullable reference types if we wish.

An error has occurred. This application may no longer respond until reloaded. Reload x