Singleton Pattern Caching in .NET C#

C#

Let's examine how to use C# to introduce the Singleton Design Pattern Real-Time Example Caching. In other words, the class will provide the fundamental Caching functionality and will be created as a Singleton. Therefore, we may add elements to the cache, update the cache, retrieve elements from the cache, remove a specific element from the cache, and remove all elements from the cache by utilizing the Singleton instance.

We talked about how the Singleton class can inherit from another class, but the Static class can never inherit from another class in our post on Singleton vs. Static Class. Now let's see how this Caching Example's Singleton class is inherited from another class.

Create the Cache Interface

First, copy and paste the following code into a class file called ICacheService.cs. The operations necessary for caching are declared in this interface. As you can see, in order to save any data in the Cache, we are utilizing the object as the argument for both keys and values.

public interface ICacheService
{
    bool Add(object key, object value);
    
    bool AddOrUpdate(object key, object value);
    
    object Get(object key);
    
    bool Remove(object key);
    
    void Clear();
}

Creating the Singleton Class

The Singleton Class must now be created by deriving from the ICacheService interface mentioned above and providing an implementation for each of the five interface methods. In order to use the following code, create a class file called CacheService.cs and copy and paste it.

public sealed class CacheService : ICacheService
{
    // Concurrent Dictionary Collection is Thread-Safe;
    // yet, it is a shared resource that requires protection in a multithreaded environment.
    private ConcurrentDictionary<object, object> _concurrentDictionary = new();

    // Initializing the variable during class startup and preparing it for use later on,
    // this variable will hold the Singleton Instance.
    private static readonly CacheService _cacheService = new();

    // The Singleton Instance will be returned by the subsequent Static Method.
    // A thread-safe method that makes use of eager loading
    public static CacheService GetInstance()
    {
        return _cacheService;
    }

    // To prevent class instantiation from outside of this class, the constructor must be private.
    private CacheService()
    {
        Console.WriteLine("Singleton cache instance created.");
        Console.WriteLine();
    }

    // The Singleton Instance can be used to access the following methods from outside of the class.

    // A Key-Value Pair can be added to the Cache using this function.
    public bool Add(object key, object value)
    {
        return _concurrentDictionary.TryAdd(key, value);
    }

    // A Key-Value Pair can be added or updated into the Cache using this function.
    // Add the key-value pair if the key is unavailable.
    // Update the key's value if it has already been added.
    public bool AddOrUpdate(object key, object value)
    {
        if (_concurrentDictionary.ContainsKey(key))
        {
            _concurrentDictionary.TryRemove(key, out _);
        }

        return _concurrentDictionary.TryAdd(key, value);
    }

    // If the specified key is in the cache, this function is used to return its value.
    // If not, return null.
    public object Get(object key)
    {
        if (_concurrentDictionary.ContainsKey(key))
        {
            return _concurrentDictionary[key];
        }

        return null!;
    }

    // Using this technique, the specified key is deleted from the cache.
    // Return true if removed; return false otherwise.
    public bool Remove(object key)
    {
        return _concurrentDictionary.TryRemove(key, out _);
    }

    // Using this technique, the specified key is deleted from the cache.
    // Return true if removed; return false otherwise.
    public void Clear()
    {
        // Eliminates every key and value from the Cache, or the ConcurrentDictionary.
        _concurrentDictionary.Clear();
    }
}

Code Explanations

  • To prevent Thread-Safety Problems when executing the program in a Multithreaded Environment, we have implemented the Singleton Design Pattern using Eager Loading in the CacheService class mentioned above.
  • Here, we store the data using key-value pairs in the ConcurrentDictionary collection. This will function similarly to a cache. We utilize the ConcurrentDictionary collection rather than the Generic Dictionary because it is by default thread-safe, meaning that we won't have any thread-safety problems while utilizing a multithread environment.
  • The Key and Value are expected parameters for the Add method. If the key does not already exist in the collection, it then adds the key and values to the ConcurrentDictionary collection. It won't add if the key already exists; in that case, it will return False.
  • The Key and Value are additional parameters that the AddOrUpdate function requires. If the key does not already exist in the collection, it then adds the key and values to the ConcurrentDictionary collection. If the key already exists, it will update the value with the new value that was received.
  • Based on the supplied key, the Get method will return the value. It returns null if the key is not present in the collection.
  • The Remove function returns true after removing the key from the Collection or Cache. It will return false if the key is not present.
  • Every key and value in the collection will be eliminated using the Clear technique.

Utilizing Caching and Singleton Instance in Client Code

The Client Code will be the Program class in our case. To learn how to do caching using the singleton class, let's change the Program class's Main method to use the singleton instance.

const string ID = "Id";
const string NAME = "Name";

// Bring the singleton cache instance
CacheService cache = CacheService.GetInstance();

// Using the Add and AddOrUpdate Method to Add Keys and Values to the Cache
Console.WriteLine("Putting Values and Keys in the Cache");
Console.WriteLine($" Putting Id in Cache: {cache.Add(ID, 1)}");
Console.WriteLine($" Putting Name in Cache: {cache.Add(NAME, "Jaimin Shethiya")}");

Console.WriteLine($" Putting Same Id Key in Cache using Add: {cache.Add(ID, 163)}");
Console.WriteLine($" Putting Same Id Key in Cache using AddOrUpdate: {cache.AddOrUpdate(ID, 163)}");

// Using the Get Method and the Keys to access values from the Cache
Console.WriteLine("\nBring Values from Cache");
Console.WriteLine($" Bring Id From Cache: {cache.Get(ID)}");
Console.WriteLine($" Bring Name From Cache: {cache.Get(NAME)}");

// Using the Remove Method to remove elements from the cache by giving the specified keys
Console.WriteLine("\nRemoving Values from Cache");
Console.WriteLine($" Remove Id: {cache.Remove(ID)}");
Console.WriteLine($" Accessing Id From Cache: {cache.Get(ID)}");

// Using the Clear Method to Remove Every Element from the Cache
cache.Clear();
Console.WriteLine("\nClearing All Keys and Values");
Console.WriteLine($" Bring Name From Cache: {cache.Get(NAME)}");

Output

We learned the new technique and evolved together.

Happy coding!


Similar Articles