☰ Brand

Persian Programmer

Persian Programmer ×
Home About Us Services and Pricing Contact Us My Resume Co-worker

Performance Wars — Null Check — C#

Performance Wars — Null Check — C#

This post is for people who are really obsessed with performance. Validating a null value can be done in different ways. For this battle, I decided to analyze the type string and an object created by me.
All the tests were done with the BenchmarkDotNet NuGet package and using the .NET 5.0 version. You can check the GitHub repository.

Strings

This test is very trivial. We have a string variable with null value. Each method checks if its value is null and if so sets the variable.

using BenchmarkDotNet.Attributes;

namespace Benchmarks.NullChecks
{
    [RankColumn]
    [SimpleJob(targetCount: 50)]
    public class NullCheckStringBenchmark
    {
        private string str;
        private string message = "value is null";

        [Benchmark]
        public void Equal_Operator()
        {
            str = null;
            if (str == null)
            {
                str = message;
            }
        }

        [Benchmark]
        public void Equals_Method()
        {
            str = null;
            if (string.Equals(str, null))
            {
                str = message;
            }
        }

        [Benchmark]
        public void ReferenceEquals_Method()
        {
            str = null;
            if (ReferenceEquals(null, str))
            {
                str = message;
            }
        }

        [Benchmark]
        public void Is_Operator()
        {
            str = null;
            if (str is null)
            {
                str = message;
            }
        }

        [Benchmark]
        public void NullOrEmpty_Method()
        {
            str = null;
            if (string.IsNullOrEmpty(str))
            {
                str = message;
            }
        }

        [Benchmark]
        public void Default_Operator()
        {
            str = null;
            if (str == default)
            {
                str = message;
            }
        }

        [Benchmark]
        public void Coalesce_Operator()
        {
            str = null;
            str = str ?? message;
        }

        [Benchmark]
        public void Ternary_Operator()
        {
            str = null;
            str = str == null ? message : str;
        }

        [Benchmark]
        public void Is_Operator_Braces()
        {
            str = null;
            if (str is not { })
            {
                str = message;
            }
        }
    }
}

As we can see there are 3 ways which are definitively the worst. The other ones are very similar and no one stands out. For me these results were great, I usually use the == operator which is in the average looking at these results.
The IsNullOrEmpty method is slower but it also checks if the string is empty. It’s interesting this gap between this method and the == operator since it has an Or condition and the first one is the null check. You can check this here.

Objects

In this case, I created a User class and define a variable with null value. Each benchmark does the same, checks if that variable is null and create a new instance in that case.

using BenchmarkDotNet.Attributes;
using Benchmarks.Models;

namespace Benchmarks.NullChecks
{
    [RankColumn]
    [SimpleJob(targetCount: 50)]
    public class NullCheckObjectBenchmark
    {
        private User user;

        [Benchmark]
        public void Equal_Operator()
        {
            user = null;
            if (user == null)
            {
                user = new User();
            }
        }

        [Benchmark]
        public void Equals_Method()
        {
            user = null;
            if (Equals(user, null))
            {
                user = new User();
            }
        }

        [Benchmark]
        public void ReferenceEquals_Method()
        {
            user = null;
            if (ReferenceEquals(user, null))
            {
                user = new User();
            }
        }

        [Benchmark]
        public void Is_Operator()
        {
            user = null;
            if (user is null)
            {
                user = new User();
            }
        }

        [Benchmark]
        public void Default_Operator()
        {
            user = null;
            if (user == default)
            {
                user = new User();
            }
        }

        [Benchmark]
        public void Coalesce_Operator()
        {
            user = null;
            user = user ?? new User();
        }

        [Benchmark]
        public void Ternary_Operator()
        {
            user = null;
            user = user == null ? new User() : user;
        }

        [Benchmark]
        public void Is_Operator_Braces()
        {
            user = null;
            if (user is not { })
            {
                user = new User();
            }
        }
    }
}

Once again there are some approaches that have had similar results in terms of time spent. However, all the cases needed basically the same time to process. There isn’t any substantial gap between them. A curious fact is that the check for objects is slower than for strings.

Conclusion

This battle was curious. We have to keep in mind that these tests were done just regarding the null comparison. For example, in the comparison between two not null string values, the results will be different.
It was really interesting to see how close were the results for these tested approaches.
My advice, after this, is to keep it simple and use the operator that you prefer. We have to be pragmatic, we are talking about nanoseconds but be careful if every nanosecond counts for your service.

0 comments

answer