48  
bestpractices
Поиск  
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

Автор:
Источник:
Просмотров:
2064

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.

Похожее
Sep 6, 2023
Author: Kenji Elzerman
To write files with C#, you have to know how to do it. The basics aren’t rocket science. There are a few lines of code you need to know. But writing and reading files with C# is something every beginning...
Apr 5, 2023
Author: Juan Alberto España Garcia
Learn the key differences between abstract classes and interfaces in C# programming, and understand when to use each one effectively. In object-oriented programming, abstract classes and interfaces serve as blueprints for creating objects in C#. While they have some similarities,...
Sep 23
Author: Tiago Martins
This is a behavioral pattern where the main goal is to split a complex job into multiple steps, each with a specific functionality. It’s really good for several types of architectures. For a system composed of various microservices with numerous...
May 31, 2023
Author: Anton Selin
LINQ (Language Integrated Query) is a powerful querying tool in .NET that allows you to perform complex queries directly in C#. The System.Linq.Expressions namespace is a part of LINQ that provides classes, interfaces, enumerations and structures to work with lambda...
Написать сообщение
Тип
Почта
Имя
*Сообщение
RSS
Если вам понравился этот сайт и вы хотите меня поддержать, вы можете
LinkedIn: Sergey Drozdov
Boosty
Donate to support the project
GitHub account
GitHub profile