Blog

Difference between IsNullOrEmpty & IsNullOrWhiteSpace

You could be familiar with these two methods of string. And today we are going to dive into them to see what the difference between them is.

What is it?

  • IsNullOrEmpty method indicates whether a specified is null or an empty string.
  • IsNullOrWhiteSpace method indicates whether a specified is null, empty, or consists of white-space characters.

How it works!

Let’s take a look at an example below.

static void Main(string[] args)
{
    Console.WriteLine(string.IsNullOrWhiteSpace("\t"));
    Console.WriteLine(string.IsNullOrEmpty("\t"));
    /* Output
    True
    False
    */

    Console.WriteLine(string.IsNullOrWhiteSpace("\n"));
    Console.WriteLine(string.IsNullOrEmpty("\n"));
    /* Output
    True
    False
    */

    Console.WriteLine(string.IsNullOrWhiteSpace(" "));
    Console.WriteLine(string.IsNullOrEmpty(" "));
    /* Output
    True
    False
    */

    Console.WriteLine(string.IsNullOrWhiteSpace(""));
    Console.WriteLine(string.IsNullOrEmpty(""));
    /* Output
    True
    True
    */
}

As you have seen, they returned 2 different results except the last one. In order to clarify this, we are now going to look at its code in more detail.

public static bool IsNullOrEmpty(string value)
{
    if (value != null)
        return value.Length == 0;
    return true;
}

public static bool IsNullOrWhiteSpace(string value)
{
    if (value == null)
        return true;
    for (int index = 0; index < value.Length; ++index)
    {
        if (!char.IsWhiteSpace(value[index]))
            return false;
    }
    return true;
}

As you can see, IsNullOrEmpty method only checks the specified string whether it will be a null value or its length will be zero.  So that’s why this method has returned False of 3 cases above. Similarly, IsNullOrWhiteSpace method also makes sure our specified string whether it is null or empty. However, it has a little different from IsNullOrEmpty method is that white-space characters are defined by the Unicode standard. The IsNullOrWhiteSpace method interprets any character that returns a value of true when it is passed to the Char.IsWhiteSpace method as a white-space character. It is kind of useful when we have a method that converts from a string to a number as below. It’s because the condition !string.IsNullOrEmpty(strValue) will return True even if strValue parameter is a white-space or character escape sequences (“\n”, “\t”, …). And we will get an exception once Convert.ToInt32 method is called. And IsNullOrWhiteSpace will address this one.

static void Main(string[] args)
{
    string strValue = "\n";
    ConvertToInt(strValue);
}

public static int ConvertToInt(string strValue)
{
    if (!string.IsNullOrEmpty(strValue))
    {
        // This will throw an exception: System.FormatException: 'Input string was not in a correct format.'
        return Convert.ToInt32(strValue);
    }

    if (!string.IsNullOrWhiteSpace(strValue))
    {
        return Convert.ToInt32(strValue);
    }

    return 0;
}

Let’s see another example below. In order to remove all leading and trailing white-space characters from the current string, we will use Trim method. However, in the case below, we will get an exception since name is null and IsNullOrWhiteSpace works.

static void Main(string[] args)
{
    string name = null;
    GetItemByName(name);
}

public static Item GetItemByName(string name)
{
    if (!string.IsNullOrEmpty(name.Trim()))
    {
        // This will throw an exception: System.NullReferenceException: 'Object reference not set to an instance of an object.'
        return new Item();
    }

    if (!string.IsNullOrWhiteSpace(name))
    {
        return new Item();
    }

    return null;
}

Conclusion

I am showing you how these two methods work. IsNullOrWhiteSpace looks better, however, it depends on our situation what we need to handle and choose which one is suited for us.

Improve Performance by caching layer

Intro #

Performance is one of the hottest topics in the software development industry. In this post, I would like to share my experience when optimizing a CRM system which serves ~1k requests/second.

Problem #

Imagine, a CRM system has ~1000 user preferences (e.g. time zone, date format, currency,…), those user preferences are stored in a SQL database, and it will be loaded every single click to reflect the preference of the user. If your web app serves ~1K requests/second, it means there are ~1M requests to the DB per second (in the worst case). At this point, you can see we can improve the performance of the app by reducing the number of requests to the database and it’s time for the caching layer to shine!.

Breaking down the problem #

We break down into smaller problems:

  1. P1: In the current system, we have a helper class, let’s say UserPreference. It exposed 2 methods: string Get(userId, key) and void Set(userId, key, value). These methods will go to the database to get or set data. How to reduce code rewriting to reduce risks? What is the backup plan if the changes cause problem in production?
  2. P2: The UserPreference class was shared to many projects (Asp.net web form, Asp.net Web API, Window service, Load balancer nodes…). So, data consistency in distributed environment is a problem.
  3. P3: In the future if we need to cache more things (Big query result, File content,…). The cache system should be easy to extend.

Solution #

To solve the problem, I create CacheBucket pattern. See the class diagram below:

cache bucket pattern

Imagine, a bucket can contain values (water, sand, oil,…) and it can contain another Bucket. This is the core concept of the CacheBucket pattern.

The CacheBucket pattern includes:

  • ICacheStorage: An interface which take responsibility to get or set cache data from a storage. (Memory, Redis, file…)
  • Client$: A place which uses the pattern.
  • CacheBucket: Manage inner buckets and cache values, it depends on ICacheStorage.
  • UserPreferenceCacheBucket and BigQueryCacheBucket: Inherit the CacheBucket class for specific ICacheStorage. In detail, the UserPreferenceCacheBucket will use InMemoryCacheStorage. On another hand, the BigQueryCacheBucket will use the RedisCacheStorage.
  • InMemoryCacheStorage and RedisCacheStorage: Implementations of the ICacheBucket

Ok! Now we will see how the CacheBucket pattern can solve the problem.

First, the CacheBucket class handled all logic about caching management so we just update small code change in the UserPreference class for adding caching into the system. For the backup solution we just add a flag (configurable value) which helps to detect if the caching system is enabled or not. In case there is a problem in production we can switch back to the old code by changing the value of the flag. So, P1 is solved.

Second, to solve the concurrency problem, we should use a centralized cache server such as Redis. Fortunately, with the CacheBucket pattern we can easily change the cache storage by implementing the ICacheStorage interface. So, P2 is solved.

Therefore, the CacheBucket pattern is very easy to extend. For example, if you want to cache the reporting result, all you need is to extend the CacheBucket class.

Benchmark #

To do the benchmark, I have created an example project. We can switch between enabling or disabling the cache. So, we can do benchmark the latency of the web application while enable/disabled the caching layer.

For the tool, I used the WRK (a HTTP Benchmarking tool) to simulate 200 connections (users) with this command:

wrk -t2 -c200 -d60s --timeout 3s http://cachebucket.com:5000/\?user_id\=1

The result #

When the caching layer is enabled, the latency is 465.46 ms and the web app can response 481.27 requests/sec. On another hand, the latency is 658.68 ms and 305.73 requests/sec when the caching layer is disabled. You can see more details below:

Without cache
caching layer disabled
With cache
caching layer enabled

How to use CacheBucket pattern: #

Install package #

To use CacheBucket pattern you need to install packages from NuGet.

There are 3 packages:

  1. CacheBucket.Core contains the core of cache bucket. In theory, you can use cache bucket pattern with this package only. However, for more convenience, you can consider other 2 packages below.
  2. CacheBucket.InMemory contains an InMemoryCacheStorage which is an implementation of ICacheStorage to store cache data in memory.
  3. CacheBucket.Factory contains some helper classes and extension methods that help to create a CacheBucket inline code. e.g: CacheBucketFactory.Create("UserPreference:1")

This is an example command to install CacheBucket.Core in “Package Management Tools”

Install-Package CacheBucket.Core #replace CacheBucket.Core by another package name to install another package.

or if you prefer the dotnet command:

dotnet add package CacheBucket.Core

Sample code #

Because of the CacheBucket class is open for extending you can create a derived class as the sample code below:

using CB.Core;
using CB.InMemory;

namespace WebApplication.Helpers {
    public class UserPreferenceCacheBucket : CacheBucket {
        public const string NAME = "UserPreference";

        public UserPreferenceCacheBucket (InMemoryCacheStorage cacheStorage) : base (NAME, cacheStorage) { }
    }
}

And then you can inject the UserPreferenceCacheBucket class into a client:

using CB.Core;
using WebApplication.Data;
using WebApplication.Data.Models;

namespace WebApplication.Helpers {
    public class UserPreferenceHelper {
        private readonly UserPreferenceCacheBucket _cacheBucket;
        private readonly ApplicationDbContext _dbContext;

        // The UserPrefrenceCacheBucket can be inject by IoC container.
        public UserPreferenceHelper (UserPreferenceCacheBucket cacheBucket, ApplicationDbContext dbContext) {
            _cacheBucket = cacheBucket;
            _dbContext = dbContext;
        }

        public string Get (int userId, string key) {
            CacheBucket userBucket = null;
            if (MvcApplication.EnableCacheBucket) {
                userBucket = _cacheBucket.In (userId.ToString ());

                // get cache value.
                var cacheValue = userBucket.GetValue (key);

                if (string.IsNullOrEmpty (cacheValue) == false) {
                    return cacheValue;
                }
            }

            // get db value.
            UserPreference userPreference = GetUserPreference (userId, key);

            if (userPreference == null) {
                return null;
            }

            // set into cache.
            userBucket?.SetValue (key, userPreference.Value);

            return userPreference.Value;
        }

        /// ...
    }
}

Conclusion #

Overall, the caching layer is simple and easy to apply but effective. You don’t need to use CacheBucket to apply caching layer, you can apply the caching pattern in your own way as long as it is the most effective way for your situation.

This is my personal experience. If you have any better way, please share it on the comments, and we can discuss later😜