LINQ explained with sketches

8/10/2022

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: https://steven-giesel.com/blogPost/8d12d9ef-c4e6-439c-9f88-46825cf35576


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

Select

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

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

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.

Zip

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

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

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

Aggregate

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.

Chunk

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

Union

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.

Intersect

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

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.

All

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

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

Prepend

Puts the given element at the beginning of the list.


Part 3

MaxBy

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

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.

ToLookup

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

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

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

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.

Skip

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

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

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."

Reverse

Returns a reversed version of the given enumeration.

First()

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()

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.

FirstOrDefault

SingleOrDefault

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.

LINQ: Select.Where or Where.Select?

LINQ is a very powerful tool for querying data. As the majority of functions are built on top of IEnumerable<T> and it, in most cases returns IEnumerable<T> as well, it is very easy to chain multiple functions together. That leaves you with a question: which one should I use, Select.Where or Where.Select?

LINQ MindMap: .NET 9 Edition

After my first LINQ MindMap: Here is an updated version that includes everything up until .NET 9.

LINQ explained with sketches - the eBook

After covering and collecting multiple parts of LINQ explained with sketches I bundled them all together in a small booklet with more explanation and code samples.

Therefore I present: LINQ explained with sketches - the eBook. Over 30 pages with smaller explanations and more LINQ operations than in my last post. Litte Bonus: There are interactive links with smaller examples to fiddle around with.

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