Hi I`m Faris!

I'm a Senior Software Engineer working with .NET Framework,.NET Core, Entity Framework, Design Pattern, SQL Server, SQLite, Web Development, And I`m Still Learning 🕶️.

Me : Assist To Simplified .NET Development, Provide .NET Best Practise,Sharing Knowledged.

Handle the collection of data it`s not what you think. Let`s handle it efficiently.

07 August 2024 by Admin
dotnetBenchmarkDotNet
...

Intro

In .NET, Enumerable.Empty<T>() is a method provided by the System.Linq namespace that returns an empty sequence of a specified type. This is useful when you need to work with an IEnumerable<T> that has no elements, such as when initializing or chaining queries.

First time with BenchmarkDotNet. Please refer to this blog Post for beginner guide on how to playaround with it.

Real Use Case

Imagine that you have a StudentRepository which return list of Students from your database. It probably have a predicate/filter (based on use case) where probably return no data.

public class StudentRepo
{
    public async Task<IEnumerable<Student>> GetStudents()
    {
        // code logic
        var result = await context.Students.TolistAsync(); //call to db
        if (result.Count == 0)
            return new List<Student>(); // here is the problem ⚠️

        return result;
    }
}

Lets Benchmark

This code defines a benchmarking class BenchmarkEmptyCollection.cs using the BenchmarkDotNet library to compare the performance of two methods for returning an empty collection of Student.cs objects.

  • ReturnNew(): Returns a void new empty List<Student>.
  • ReturnEmpty(): Returns avoid sequence using Enumerable.Empty<Student>()
  • The [MemoryDiagnoser] attribute is used to analyze memory usage, and the [Benchmark(Baseline = true)]
[MemoryDiagnoser]
public class BenchmarkEmptyCollection
{
    private IEnumerable<Student> _students;
    [Benchmark(Baseline = true)]
    public void ReturnNew()
    {
        _students = new List<Student>();
    }

    [Benchmark]
    public void ReturnEmtpy()
    {
        _students = Enumerable.Empty<Student>();
    }

}

Note : Make sure run in release mode

Result

The results really shock me since the difference is significantly big. ReturnEmtpy() is significantly faster and more memory-efficient than ReturnNew(). It takes about 31% of the time of ReturnNew() and performs no allocations 🤩, whereas ReturnNew() allocates 32 bytes and involves some garbage collection which is bad in performance matters. This demonstrates that Enumerable.Empty<Student>() is more efficient for returning an empty sequence compared to creating a new List<Student>.

| Method      | Mean     | Error     | StdDev    | Ratio | RatioSD | Gen0   | Allocated | Alloc Ratio |
|------------ |---------:|----------:|----------:|------:|--------:|-------:|----------:|------------:|
| ReturnNew   | 6.693 ns | 0.1515 ns | 0.2268 ns |  1.00 |    0.05 | 0.0153 |      32 B |        1.00 |
| ReturnEmtpy | 2.074 ns | 0.0611 ns | 0.0572 ns |  0.31 |    0.01 |      - |         - |        0.00 |

Only Enumerable can used this Empty? How about Array?

Absolutely guys ❤️. Array type can use as below :

// code ..
 return Array.Empty<T>(); // change <T> to your type
// code ..

Summary

Our example prove that by tweaking few code can have a significant boost in our code. Moreover, this shows that instead of creating a new empty collection (like an empty array or list), Enumerable.Empty<T>() provides a more efficient way to represent an empty sequence. It avoids unnecessary allocations and provides a shared, static instance of an empty sequence, which can improve performance and reduce memory usage.

Source Code ❤️