Search  
Always will be ready notify the world about expectations as easy as possible: job change page
Jan 7

6 common LINQ mistakes you might be doing

Source:
Views:
2256

LINQ

Few days ago I stopped myself while writing code. I wrote my LINQ filtering wrong.

items.Where(x => x > 0).Any();

(Obviously, it’s a pseudo code)

I realized my mistake immediately, changed the code, and went on with my day.

Then it started bugging me. What other LINQ methods could I have misused in the similar way? Not noticing the mistake and thinking I picked the best solution for the problem I was facing?

I started digging around in documentation, in my and others’ code bases, and here are some common LINQ mistakes that I have found.

1. Single() vs First()

// Incorrect
var vehicle1 = vehicles.Single(x => x.Id == 42);
var vehicle2 = vehicles.SingleOrDefault(x => x.Id == 42);

// Correct
var vehicle1 = vehicles.First(x => x.Id == 42);
var vehicle2 = vehicles.FirstOrDefault(x => x.Id == 42);

Why? What’s the difference?

  • Incorrect: Single() or SingleOrDefault() makes sure that there is just one and only one match in the set of vehicles — there are no two or more vehicles with Id 42.
  • Correct: First() or FirstOrDefault() returns the first match — first vehicle with Id 42.

In most cases you are not ensuring that there is just one match. Often, that shouldn’t be the case — your primary key (Id) should be unique.

In terms of SQL it would look like this:

  • Single() or SingleOrDefault()SELECT 2 …
  • First() or FirstOrDefault()SELECT 1 …

You can already see where this is going — the first one should have worse performance.

What about benchmarks (which you can run yourself here)?

Sample size of 10,000
Sample size of 10,000

Now we have some proof — use First() or FirstOrDefault(). Grab the Single() and SingleOrDefault() only when you really need it.

2. Where().First()

// Incorrect
var vehicle1 = vehicles
  .Where(x => x.Brand == "Porsche")
  .First();
var vehicle2 = vehicles
  .Where(x => x.Brand == "Porsche")
  .FirstOrDefault();

// Correct
var vehicle1 = vehicles.First(x => x.Brand == "Porsche");
var vehicle2 = vehicles.FirstOrDefault(x => x.Brand == "Porsche");

What’s the difference this time?

  • Incorrect: First, get all the vehicles with the brand that equals to “Porsche”. Then retrieve the first of those vehicles.
  • Correct: Get the first vehicle with the brand that equals to “Porsche” — then stop there.

Benchmark time? Benchmark time.

Sample size of 10,000
Sample size of 10,000

There is a small difference which proves the point — First() or FirstOrDefault() does the same as Where().First() — but with better performance.

3. Where().Where()

// Incorrect
var porscheVehicles = vehicles
  .Where(x => x.Brand == "Porsche")
  .Where(x => x.ProductionYear > 2010);

// Correct
var porscheVehicles = vehicles
  .Where(x => x.Brand == "Porsche" && x.ProductionYear > 2010);

Explanation:

  • Incorrect: Retrieve all the vehicles with brand Porsche, and out of them take vehicles with production year greater than 2010.
  • Correct: Retrieve all vehicles with brand Porsche with production year greater than 2010.

In SQL it could end up like SELECT * FROM (SELECT FROM …).

And here we go — another benchmark.

Sample size 10,000
Sample size 10,000

Contrary to most benchmarks where just numbers are used — this time it’s a benchmark that is similar to the example shown above — using vehicle objects. As a result, more memory is allocated. Code can be seen here.

As you can see, we have another proof — using just one Where is much more efficient.

4. OrderBy().OrderBy()

// Incorrect
var orderedVehicles = vehicles
  .OrderBy(x => x.Brand)
  .OrderBy(x => x.ProductionYear);

// Correct
var orderedVehicles = vehicles
  .OrderBy(x => x.Brand)
  .ThenBy(x => x.ProductionYear);

This is not a performance issue but instead a logical issue. What exactly is happening in the example above?

  • Incorrect: Sort vehicles by brand. Then ignore the previous sort and re-sort the vehicles by production year.
  • Correct: Sort vehicles by brand. Then take the previous sort into account and then also sort them by production year.

Note that the same applies to OrderByDescending() and ThenByDescending().

5. Empty Count()

// Incorrect
var count1 = vehiclesList.Count();
var count2 = vehiclesArray.Count();

// Correct
var count1 = vehiclesList.Count;
var count2 = vehiclesArray.Length;

What’s the problem here?

We shouldn’t be using Enumerable.Count() for arrays and any collections that implement ICollection<T> or ICollection interface — these include List<T> and Dictionary<T>.

We should be instead using Count property for ICollection implementation and Length for arrays.

Although, when we look into the LINQ source code we can see the following:

public static int Count<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
        throw Error.ArgumentNull("source");
 
    ICollection<TSource> collectionoft = source as ICollection<TSource>;
    if (collectionoft != null)
        return collectionoft.Count;
 
    ICollection collection = source as ICollection;
    if (collection != null)
        return collection.Count;
 
    int count = 0;
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
        checked
        {
            while (e.MoveNext()) count++;
        }
    }

    return count;
}

When it can, it will early return Count instead of doing O(n) operation.

6. Count() instead of Any() or All()

// Incorrect
bool any1 = vehicles.Count() > 0;
bool any2 = vehicles.Count(x => x.Brand = "Porsche") > 0;
bool all = vehicles.Count(x => x.Brand = "Porsche") == vehicles.Count();

// Correct
bool any1 = vehicles.Any();
bool any2 = vehicles.Any(x => x.Brand = "Porsche");
bool all = vehicles.All(x => x.Brand = "Porsche");

Well, first of all I am curious if you noticed I used Count() instead of Count. Props to you if you did — that could be the first incorrect point.
(Although, from the small code snippet, it’s not obvious if it’s List<T> or not)

Difference?

  • Incorrect: First do a full iteration of the (matching) vehicles. Then compare the amount.
  • Correct: Check if there is any vehicle that matches the condition — then stop. In case of All — stop when vehicle doesn’t match the condition.

When it comes to All there are other problems that we won’t get into now.

What about performance?

Sample size of 10,000
Sample size of 10,000

As you can see, operations with Count() are much slower.

Note that Count() compared to Any() is about the same — that’s because List<T> was used — Count() returns Count property.

When it comes to Any() and All() we managed to cut a lot of time. Note that, in the worst-case scenario these operations are O(n) as well and might take the same amount of time as using Count(). We should still rather use Any() and All() in hopes of making our code perform better.

Final words

While most LINQ providers optimize in most of the cases, it’s important to know about these limitations and differences. It’s important to know which LINQ method to use and when as it makes our code more readable and performs better.

I knew about most of these mistakes (That didn’t stop me from making them again and again). Did you? Do you know any other LINQ mistakes that people make often? Share them with us.

Remember, you can run all of the benchmarks yourself here.

Similar
Jul 29
Author: C# Programming Obinna “Anderson” Asiegbulam
Introduction Open Data Protocol (OData) is a standard protocol for building and consuming RESTful APIs. It extends traditional REST-based data access techniques by providing a uniform way to expose, structure, and manipulate data. Originally developed by Microsoft, OData is widely...
15 марта
Автор: Рустем Галиев
Blazor — это технология, позволяющая создавать клиентские веб-приложения с использованием C# и .NET, а не JavaScript. Blazor может запускать ваш код одним из двух способов. Blazor WebAssembly выполняет код C# на стороне клиента в любом современном браузере, поддерживающем WebAssembly. Blazor...
Dec 16
Author: Onix React
JavaScript is one of the most popular programming languages in web development, known for its flexibility and wide application across browsers and servers. But with great power comes great responsibility — maintaining clean, efficient, and readable code is crucial, especially...
Oct 26, 2023
Author: Genny Allcroft
Key strategic and tactical considerations to take when building a new product with the domain-driven design concepts in mind I have just finished reading Learning Domain-Driven Design by Vlad Khononov. It’s quite a short book (c. 300 pages) aiming to...
Send message
Type
Email
Your name
*Message