How to use the object pool design pattern in C#

Boost application performance by using the object pool design pattern to recycle objects that are expensive to create

How to use the object pool design pattern in C#
Kevin Dooley (CC BY 2.0)

When building applications, you often come across objects that are quite expensive to create. In some scenarios, the cost of creating new objects is high enough to impact application performance. Here’s where the object pool design pattern comes to the rescue. 

The object pool design pattern is a creational design pattern that is used to recycle objects rather than recreate them each time the application needs them. By keeping reusable instances of objects in a resource pool, and doling them out as needed, this pattern helps to minimize the overhead of initializing, instantiating, and disposing of objects and to boost the performance of your application.

When the application requests an object, and the object is available from the pool, it is returned from the pool. If the object of the requested type is not available from the pool, then a new instance of the object is created and returned. When the application no longer needs the object, the object is sent back to the pool.

The minimum and maximum number of objects that an object pool can hold is configurable. If the application needs an object from the pool but the maximum number of objects has been allocated, a typical custom object pool implementation can adopt one or more of the following strategies:

  1. Return null or throw an exception
  2. Block the call until an object is available
  3. Increase the pool size to accommodate more objects

An object pool is similar to the database connection pool. Just as the connection pool controls the maximum number of connections to the database, the object pool controls the number of class instances that the application will use.

Creating a generic object pool in C#

Now that we know the basics, let’s jump into the implementation. When implementing the object pool design pattern, we need to consider reusability, simplicity, configurability, and even factors such as thread safety.

In this example we will take advantage of a ConcurrentBag class to store objects. Note that the ConcurrentBag class in the System.Collections.Concurrent namespace provides a lock-free, thread-safe, unordered collection of elements. Also note that the insertion and removal of objects to and from a ConcurrentBag is very fast—especially if the same thread is trying to insert and remove items from the collection at the same time.

Here is the structure of our custom ObjectPool class. Note the usage of the ConcurrentBag instance for storing the objects.

public class ObjectPool<T> where T : new()
    {
        private readonly ConcurrentBag<T> items = new ConcurrentBag<T>();
        private int counter = 0;
        private int MAX = 10;
        public void Release(T item)
        {
            //TO DO          
        }
        public T Get()
        {
           //TO DO
        }
    }

The following code snippet illustrates an implementation of the Get method. The Get method returns an instance from the object pool if one is available. If none is available, a new object is created and returned. In both these scenarios, the counter variable is increased or decreased as appropriate. Note that because we are using a concurrent collection, i.e., ConcurrentBag in this example, concurrency is taken care of.

public T Get()
        {
            T item;
            if (items.TryTake(out item))
            {
                counter—;
                return item;
            }
            else
            {
                T obj = new T();
                items.Add(obj);
                counter++;
                return obj;
            }
        }

The MAX integer variable is hard-coded here, but you can make it configurable. This class is not sealed or static, so you can extend it as you like.

The Release method is used to release objects that are no longer needed back to the object pool. It checks if the value of the counter variable is less than the value of the MAX variable, and if so, adds the object passed to it as a parameter to the collection.

public void Release(T item)
        {
            if(counter < MAX)
            {
                items.Add(item);
                counter++;
            }           
        }

Assuming that you have created a class called MyClass, here is how you can add it to the object pool using the ObjectPool class.

static void Main(string[] args)
        {
            ObjectPool<MyClass> objPool = new ObjectPool<MyClass>();
            MyClass obj = objPool.Get();
            objPool.Release(obj);
            Console.Read();
        }

You can modify this custom object pool implementation to allow the minimum and maximum sizes of the pool to be read from a configuration file. As part of initialization of the object pool, you can also ensure that the pool contains the minimum number of objects in it.

Object pools help to reduce resource overhead when you need multiple instances of a class that are expensive to create or manage. If your application involves instantiating the same classes over and over again, use this design pattern to ensure optimal performance.