In case you find the Monday Links series for the first time: these are five links from past weeks that I found interesting (and worth sharing) while procastinating surfing the Web. This is not a link-building scheme, I only read and liked these articles.
The Secret Art of Storytelling in Programming by Yehonathan Sharvit
This presentation starts with the author sharing his struggle to read books as a kid. And later read code as a programmer and contracts as a consultant.
The main message from this presentation is how memory, attention, and structure spans relate to coding. The author presents three coding style principles that respect mind spans:
Use small functions
Make every line in a function have the same level of abstraction
Interviewing is broken. We all agree. But we don’t know how to fix it. Brain teasers, IQ tests, pair programming, algorithms? This post presents an alternative: inspect the candidate GitHub and public work, ask him to review some piece of code, add unit tests or do some refactors. That sounds like a better idea! Read full article
One of the things we learn while working for a company is office politics. Basically, how to say things and to step away from certain situations. I learned from a coworker to say “I don’t have enough information to answer that question” instead of a simple “I don’t know.” You will find more lines like that one in this post. Read full article
How to feel engaged at work: a software engineer’s guide
Let’s be honest. It’s rewarding when we see the impact of our work. But, often, all days look almost the same. Another JIRA ticket for a production issue. Another meeting that could have been an email. This article shows four ideas to spice things up. Read full article
The Toxic Grind
This article talks about success, hard work, and work-life balance. This is my favorite line: “We should glorify the journey of achieving something meaningful, not the dream of wealth and power. Glorify the skills you build along the way, not the shortcuts you take.”Read full article
Voilà! Another Monday Links. What do you do to feel engaged at your work? Have you been asked to solve LeetCode questions during interviews? Would you like to do something different in future interviews?
This is not one of the most used LINQ methods. We won’t use it every day. But, it’s handy for some scenarios. Let’s learn how to use the Aggregate method.
The Aggregate method applies a function on a collection carrying the result to the next element. It “aggregates” the result of a function over a collection.
The Aggregate method takes two parameters: a seed and an aggregating function that takes the accumulated value and one element from the collection.
How does Aggregate work?
Let’s reinvent the wheel to understand Aggregate by finding the maximum rating in our movie catalog. Of course, LINQ has a Max method. And, .NET 6 introduced new LINQ methods, among those: MaxBy.
varmovies=newList<Movie>{newMovie("Titanic",1998,4.5f),newMovie("The Fifth Element",1997,4.6f),newMovie("Terminator 2",1991,4.7f),newMovie("Avatar",2009,5),newMovie("Platoon",1986,4),newMovie("My Neighbor Totoro",1988,5)};varmaxRating=movies.Aggregate(0f,(maxSoFar,movie)=>MaxBetween(maxSoFar,movie.Rating));// ^^^^^^^^^Console.WriteLine($"Maximum rating on our catalog: {maxRating}");// Output:// Comparing 0 and 4.5// Comparing 4.5 and 4.6// Comparing 4.6 and 4.7// Comparing 4.7 and 5// Comparing 5 and 4// Comparing 5 and 5// Maximum rating on our catalog: 5Console.ReadKey();floatMaxBetween(floatmaxSoFar,floatrating){Console.WriteLine($"Comparing {maxSoFar} and {rating}");returnrating>maxSoFar?rating:maxSoFar;}recordMovie(stringName,intReleaseYear,floatRating);
Notice we used Aggregate() with two parameters: 0f as the seed and the delegate (maxSoFar, movie) => MaxBetween(maxSoFar, movie.Rating) as the aggregating function. maxSoFar is the accumulated value from previous iterations, and movie is the current movie while Aggregate iterates over our list. The MaxBetween() method returns the maximum between two numbers.
Notice the order of the debugging messages we printed every time we compare two ratings in the MaxBetween() method.
On the first iteration, the Aggregate() method executes the MaxBetween() aggregating function using the seed (0f) and the first element (“Titanic” with 4.5) as parameters.
Aggregate first iteration
Next, it calls MaxBetween() with the previous result (4.5) as the maxSoFar and the next element of the collection (“The Fifth Element” with 4.6f).
Aggregate second iteration
In the last iteration, Aggregate() finds the maxSoFar from all previous iterations and the last element (“My Neighbor Totoro” with 5). And it returns the last value of maxSoFar as a result.
Aggregate last iteration
In our example, we used Aggregate() with a seed. But, Aggregate() has an overload without it, then it uses the first element of the collection as the seed. Also, Aggregate() has another parameter to transform the result before returning it.
Voilà! That’s how the Aggregate method works. Remember, it returns an aggregated value from a collection instead of another collection. This is one of those methods we don’t use often. I’ve used it only a couple of times. One of them was in my parsing library, Parsinator, to apply a list of modification functions on the same input object here.
Want to write more expressive code for collections? Join my course, Getting Started with LINQ on Udemy and learn everything you need to know to start working productively with LINQ—in less than 2 hours.
“Don’t use libraries you can’t read their source code.”
That’s a bold statement I found and shared in a past Monday Links episode. Inspired by that, let’s see what’s inside the new LINQ DistinctyBy method.
What DistinctBy does
DistinctBy returns the objects containing unique values based on one of their properties. It works on collections of complex objects, not just on plain values.
varmovies=newList<Movie>{newMovie("Schindler's List",1993,8.9f),newMovie("The Lord of the Rings: The Return of the King",2003,8.9f),newMovie("Pulp Fiction",1994,8.8f),newMovie("Forrest Gump",1994,8.7f),newMovie("Inception",2010,8.7f)};// Here we use the DistinctBy method with the ReleaseYear propertyvardistinctByReleaseYear=movies.DistinctBy(movie=>movie.ReleaseYear);// ^^^^^^^^^^foreach(varmovieindistinctByReleaseYear){Console.WriteLine($"{movie.Name}: [{movie.ReleaseYear}]");}// Output:// Schindler's List: [1993]// The Lord of the Rings: The Return of the King: [2003]// Pulp Fiction: [1994]// Inception: [2010]recordMovie(stringName,intReleaseYear,floatScore);
We used the DistinctBy method on a list of movies. We didn’t use it on a list of released years to then find one movie for each unique release year found.
Before looking at DistinctBy source code, how would you implement it?
This is the source code for the DistinctBy method. Source
DistinctBy source code
Well, it doesn’t look that complicated. Let’s go through it.
1. Iterating over the input collection
First, DistinctBy() starts by checking its parameters and calling DistinctByIterator().
This is a common pattern in other LINQ methods: Checking parameters in one method and then calling a child iterator method to do the actual logic. (See 1. in the image above)
Then, the DistinctByIterator() initializes the underling enumerator of the input collection with a using declaration. The IEnumerable type has a GetEnumerator() method. (See 2.)
The IEnumerator type has:
a MoveNext() method to advance the enumerator to the next position
a Current property to hold the element at the current position.
If a collection is empty or if the iterator reaches the end of the collection, MoveNext() returns false. And, when MoveNext() returns true, Current gets updated with the element at that position. Source
Then, to start reading the input collection, the iterator is placed at the initial position of the collection calling MoveNext(). (See 3.) This first if avoids allocating memory by creating a set in the next step if the collection is empty.
2. Keeping only unique values
After that, DistinctByIterator() creates a set with a default capacity and an optional comparer. This set keeps track of the unique keys already found. (See 4.)
DefaultInternalSetCapacity = 7
The next step is to read the current element and add its key to the set. (See 5.)
If a set doesn’t already contain the same element, Add() returns true and adds it to the set. Otherwise, it returns false. And, when the set exceeds its capacity, the set gets resized. Source
If the current element’s key was added to the set, the element is returned with the yield return keywords. This way, DistinctByIterator() returns one element at a time.
Step 5 is wrapped inside a do-while loop. It runs until the enumerator reaches the end of the collection. (See 6.)
Voilà! That’s the DistinctBy source code. Simple but effective. Not that intimidating, after all. The trick was to use a set.
Want to write more expressive code for collections? Join my course, Getting Started with LINQ on Udemy and learn everything you need to know to start working productively with LINQ—in less than 2 hours.
LINQ isn’t a new feature in the C# language. It was released back in C# version 3.0 in the early 2000s.
And, after more than ten years, it was finally updated with the .NET 6 release. These are four of the new LINQ methods and overloads in .NET 6.
.NET 6 introduced new LINQ methods like Chunk, DistinctBy, MinBy, and MaxBy. Also, new overloads to existing methods like Take and FirstOrDefault. Before this update, to use similar features, custom LINQ methods or third-party libraries were needed.
1. Chunk
Chunk splits a collection into buckets or “chunks” of at most the same size. It receives the chunk size and returns a collection of arrays.
Let’s say we want to watch all movies we have in our catalog. But, we can only watch three films on a single weekend. Let’s use the Chunk method for that.
varmovies=newList<Movie>{newMovie("Titanic",1998,4.5f),newMovie("The Fifth Element",1997,4.6f),newMovie("Terminator 2",1991,4.7f),newMovie("Avatar",2009,5),newMovie("Platoon",1986,4),newMovie("My Neighbor Totoro",1988,5)};// Split the movies list into chunks of three moviesvarchunksOf3=movies.Where(movie=>movie.Rating>4.5f).Chunk(3);// <--foreach(varchunkinchunksOf3){PrintMovies(chunk);}// Output:// The Fifth Element,Terminator 2,Avatar// My Neighbor TotorostaticvoidPrintMovies(IEnumerable<Movie>movies){Console.WriteLine(string.Join(",",movies.Select(movie=>movie.Name)));}recordMovie(stringName,intReleaseYear,floatRating);
We used three recent C# features: the Top-level statements, records from C# 9.0, and global using statements from C# 10.0. We don’t need all the boilerplate code to write a Console application anymore.
Chunk returns “chunks” with at most the given size. It returns fewer elements in the last bucket when there aren’t enough elements in the source collection.
2. DistinctBy
Unlike Distinct, DistinctBy receives a delegate to select the property to use as the comparison key and returns the objects containing the distinct values, not only the distinct values themselves.
Let’s find the movies containing unique ratings, not just the ratings.
Also, there are similar alternatives to existing methods such as MinBy, MaxBy, ExceptBy, IntersectBy, and UnionBy. They work with a delegate to select a property to use as the comparison key and return the “containing” objects, not only the result.
Take receives a range of indexes to pick a slice of the input collection, not only the first consecutive elements. Take with a range replaces Take followed by Skip to choose a slice of elements.
Take(^5..3) selects elements starting from the fifth position from the end (^5) up to the third position from the start (3). We didn’t need to use the Skip method for that.
Now that we have Take with ranges is easier to find the last “n” elements of a collection: Take(^n...).
Before this .NET update, we had to use Skip(movies.Count - 3).Take(3). Take did all the work.
4. XOrDefault methods with an optional default value
FirstOrDefault has a new overload to return a default value when the collection is empty or doesn’t have any elements that satisfy the given condition. Other methods with the suffix ‘OrDefault’ have similar overloads.
Let’s find in our catalog of movies a “perfect” film. Otherwise, let’s return our favorite movies from all times.
Before this update, we had to check if FirstOrDefault returned a non-null value or we had to use the LINQ DefaultIfEmpty method. Like this,
varallTimesFavorite=newMovie("Back to the Future",1985,5);// Using the Null-coalescing assignment ??= operatorvarfavorite=movies.FirstOrDefault(movie=>movie.Rating==10);favorite??=allTimesFavorite;// ^^^// Or// Using the DefaultIfEmpty methodvarfavorite=movies.Where(movie=>movie.Rating==10).DefaultIfEmpty(allTimesFavorite)// ^^^^^.First();
With the new overload, we pass a safe default. Like this,
varallTimesFavorite=newMovie("Back to the Future",1985,5);varmovies=newList<Movie>{newMovie("Titanic",1998,4.5f),newMovie("The Fifth Element",1997,4.6f),newMovie("Terminator 2",1991,4.7f),newMovie("Avatar",2009,5),newMovie("Platoon",1986,4),newMovie("My Neighbor Totoro",1988,5)};// We have a safe default now. See the second parametervarfavorite=movies.FirstOrDefault(movie=>movie.Rating==10,allTimesFavorite);// <--// We don't need to check for null hereConsole.WriteLine(favorite.Name);// Output:// Back to the future
Notice the second parameter we passed to the FirstOrDefault method.
To use these new methods and overloads, install the latest version of the .NET SDK from the .NET official page and use as target framework at least .net6 in your project files.
Here’s a sample csproj file for a Console app using .net6,
Voilà! These are four new LINQ methods released in the .NET 6 updated. FirstOrDefault with a safe default helps us prevent one of the common LINQ mistakes: using the XOrDefault methods without null checking afterwards.
Want to write more expressive code for collections? Join my course, Getting Started with LINQ on Udemy and learn everything you need to know to start working productively with LINQ—in less than 2 hours.
It’s easy to start working with LINQ to replace for, foreach, and other loops. With a handful of LINQ methods, we have our backs covered.
But, often, we make some common mistakes when working with LINQ. Here are five common mistakes we make when working with LINQ for the first time and how to fix them.
Mistake 1: Use Count instead of Any
We should always prefer Any over Count to check if a collection is empty or has at least one element that meets a condition.
Let’s write,
movies.Any()
Instead of,
movies.Count()>0
Any returns when it finds at least one element.
Count could use the size of the underlying collection. But, it could also evaluate the entire LINQ query for other collection types. And this could be a performance hit for large collections.
Mistake 2: Use Where followed by Any
We can use a condition with Any directly, instead of filtering first with Where to then use Any.
Let’s write,
movies.Any(movie=>movie.Rating==5)
Instead of,
movies.Where(movie=>movie.Rating==5).Any()
The same applies to the Where method followed by FirstOrDefault, Count, or any other method that receives a filter condition.
Let’s use the filtering condition directly instead of relying on the Where method first.
Mistake 3: Use FirstOrDefault instead of SingleOrDefault to find unique values
We should prefer SingleOrDefault over FirstOrDefault to find one and only one element matching a condition inside a collection.
Let’s write,
varmovies=newList<Movie>{newMovie("Titanic",1998,4.5f),newMovie("The Fifth Element",1995,4.6f),newMovie("Terminator 2",1999,4.7f),newMovie("Avatar",2010,5),// ^^^^^newMovie("Platoon",1986,4),newMovie("My Neighbor Totoro",1988,5)// ^^^^^// We have a tie here...};// SigleOrDefault expects only one element...but there are two of themvartheBest=movies.SingleOrDefault(movie=>movie.Rating==5);// ^^^^^^// System.InvalidOperationException: 'Sequence contains more than one matching element'//Console.WriteLine($"{theBest.Name}: [{theBest.Rating}]");recordMovie(stringName,intReleaseYear,floatRating);
Instead of,
varmovies=newList<Movie>{newMovie("Titanic",1998,4.5f),newMovie("The Fifth Element",1995,4.6f),newMovie("Terminator 2",1999,4.7f),newMovie("Avatar",2010,5),// ^^^^^newMovie("Platoon",1986,4),newMovie("My Neighbor Totoro",1988,5)// ^^^^^// We have a tie here...};// FirstOrDefault remains quiet if there's more than one matching element...vartheBest=movies.FirstOrDefault(movie=>movie.Rating==5);// ^^^^^Console.WriteLine($"{theBest.Name}: [{theBest.Rating}]");recordMovie(stringName,intReleaseYear,floatRating);
SigleOrDefault throws an exception when it finds more than one element matching a condition. But, with multiple matching elements, FirstOrDefault returns the first of them without signaling any problem.
Let’s pick between FirstOrDefault and SingleOrDefault to show the query’s intent. Let’s prefer SingleOrDefault to retrieve a unique matching element from a collection.
To guarantee that there is a single element in a collection, Single and SingleOrDefault have to evaluate the LINQ query over the entire collection. And, again, this could be a performance hit for large collections.
Mistake 4: Use FirstOrDefault without null checking
Let’s always check if we have a result when working with FirstOrDefault, LastOrDefault, and SingleOrDefault.
When any of those three methods don’t find results, they return the default value of the collection type.
For objects, the default value would be a null reference. And, do you know what happens when we access a property or method on a null reference?… Yes, It throws the fearsome NullReferenceException. Arrggg!
We have this mistake in the following code sample. We forgot to check if the worst variable has a value. An if (worst != null) would solve the problem.
varmovies=newList<Movie>{newMovie("Titanic",1998,4.5f),newMovie("The Fifth Element",1995,4.6f),newMovie("Terminator 2",1999,4.7f),newMovie("Avatar",2010,5),newMovie("Platoon",1986,4),newMovie("My Neighbor Totoro",1988,5)};varworst=movies.FirstOrDefault(movie=>movie.Rating<2);// We forgot to check for nulls after using FirstOrDefault// It will break Console.WriteLine($"{worst.Name}: [{worst.Rating}]");// ^^^^^^^^^^^^ // System.NullReferenceException: 'Object reference not set to an instance of an object.'//// worst was null.recordMovie(stringName,intReleaseYear,floatRating);
We wrote a LINQ query with FirstOrDefault looking for the first movie with a rating lower than 2. But, we don’t have any movie that matches that condition. FirstOrDefault returned null, and we forgot to check if the worst variable was different from null before using it.
There are other alternatives to get rid of the NullReferenceException when using FirstOrDefault:
It means the actual result of a LINQ query is evaluated when we loop through the result, not when we declare the query. And it’s evaluated every time we loop through it.
Let’s avoid looping through the result of a LINQ query multiple times expecting it to be cached the first time we run it.
varmovies=newList<Movie>{newMovie("The Fifth Element",1995,4.6f),newMovie("Platoon",1986,4),newMovie("My Neighbor Totoro",1988,5)};varfavorites=movies.Where(movie=>{// Let's put a debugging message here...Console.WriteLine("Beep, boop...");returnmovie.Rating==5;});// 1. Let's print our favorite moviesforeach(varmovieinfavorites){Console.WriteLine($"{movie.Name}: [{movie.Rating}]");}Console.WriteLine();// 2. Let's do something else with our favorite moviesforeach(varmovieinfavorites){Console.WriteLine($"Doing something else with {movie.Name}");}Console.ReadKey();recordMovie(stringName,intReleaseYear,floatRating);// Output// Beep, boop...// Beep, boop...// Beep, boop...// My Neighbor Totoro: [5]//// Beep, boop...// Beep, boop...// Beep, boop...// Doing something else with My Neighbor Totoro
We wrote a debugging statement inside the Where method, and we looped through the result twice. The output shows the debugging statements twice. One for every time we looped through the result.
There was no caching whatsoever. The LINQ query was evaluated every time.
Instead of expecting a LINQ query to be cached, we could use ToList or ToArray to break the lazy evaluation. This way, we force the LINQ query to be evaluated only once - when we declare it.
varmovies=newList<Movie>{newMovie("The Fifth Element",1995,4.6f),newMovie("Platoon",1986,4),newMovie("My Neighbor Totoro",1988,5)};varfavorites=movies.Where(movie=>{// Let's put a debugging message here...Console.WriteLine("Beep, boop...");returnmovie.Rating==5;}).ToList();// ^^^^^^// We break the lazy evaluation with ToList// 1. Let's print our favorite moviesforeach(varmovieinfavorites){Console.WriteLine($"{movie.Name}: [{movie.Rating}]");}Console.WriteLine();// 2. Let's do something else with our favorite moviesforeach(varmovieinfavorites){Console.WriteLine($"Doing something else with {movie.Name}");}Console.ReadKey();recordMovie(stringName,intReleaseYear,floatRating);// Output// Beep, boop...// Beep, boop...// Beep, boop...// My Neighbor Totoro: [5]//// Doing something else with My Neighbor Totoro
Notice the output only shows the debugging messages once, even though we looped through the collection twice. We forced the query to be evaluated only once with the ToList method.
Voilà! Those are the five most common LINQ mistakes. They seem silly, but we often overlook them. Especially we often forget about the lazy evaluation of LINQ queries.
Want to write more expressive code for collections? Join my course, Getting Started with LINQ on Udemy and learn everything you need to know to start working productively with LINQ—in less than 2 hours.