Sunday, June 1, 2008

C# Circular Buffer - First in first out (Not thread safe)

I needed a circular buffer for c# and I couldn't find it on the web. I implemented my version and here it is:

Note: There is no head and tail on this buffer. All you can do is write and read forever. Also, this buffer is not thread safe.

using System;
using System.Collections.Generic;
using System.Text;

namespace Utezduyar.IO.CoreCommunicator
{
    /// <summary>
    /// A circular buffer object.
    /// </summary>
    /// <example>
    /// CircularBuffer<byte> buffer = new CircularBuffer<byte>(3);
    /// buffer.Add(0x41);
    /// buffer.Add(0x42);
    /// buffer.Add(0x43);
    /// buffer.Add(0x44);
    /// buffer.Add(0x45);
    /// 
    /// Console.WriteLine("0:" + buffer.Get(0).ToString("X2"));
    /// Console.WriteLine("1:" + buffer.Get(1).ToString("X2"));
    /// Console.WriteLine("2:" + buffer.Get(2).ToString("X2"));
    /// Console.WriteLine("3:" + buffer.Get(3).ToString("X2"));
    /// Console.WriteLine("4:" + buffer.Get(4).ToString("X2"));
    /// 
    /// // Output:
    /// // 0:45
    /// // 1:44
    /// // 2:43
    /// // 3:45
    /// // 4:44
    /// </example>
    public class CircularBuffer<T>
    {
        private T[] buffer; // Buffer
        private int bufferIndex = 0; // Latest index of the buffer

        /// <summary>
        /// Constructor to initialize the buffer
        /// </summary>
        /// <param name="size">Size of the buffer</param>
        public CircularBuffer(int size)
        {
            // Parameter check
            if (size <= 0)
                throw new ArgumentException("Size must be greater than 0");

            // Assing variables
            buffer = new T[size];
        }

        /// <summary>
        /// Default constructor. Initializes a <c>CircularBuffer</c>
        /// that can hold 256 items in it.
        /// </summary>
        public CircularBuffer() : this (256)
        { 
        
        }

        /// <summary>
        /// Returns the size of the buffer
        /// </summary>
        /// <remarks>
        /// This function returns only the size of the buffer.
        /// This function can not be used to determine how 
        /// many bytes are in the buffer.
        /// </remarks>
        public int Length
        {
            get
            {
                return this.buffer.Length;
            }
        }

        /// <summary>
        /// Adds one item to the next available place in the buffer
        /// </summary>
        /// <remarks>
        /// If the buffer is full, it overwrites 
        /// the very first item.
        /// </remarks>
        /// <param name="item">Item to add</param>
        public void Add(T item)
        {
            if (bufferIndex == this.Length)
                bufferIndex = 0;

            buffer[bufferIndex++] = item;
        }

        /// <summary>
        /// Used to retrieve item array from the buffer.
        /// </summary>
        /// <param name="numberOfItems">Number of items needed.</param>
        /// <returns>Array of the item type. The array lenght 
        /// is same as <c>numberOfItems</c>.
        /// </returns>
        public T[] GetItems(int numberOfItems)
        {
            // Check arguments
            if (numberOfItems <= 0)
                throw new ArgumentException("numberOfItems needs to be greater than zero");

            // Create the byte buffer. This byte buffer will be returned
            T[] items = new T[numberOfItems];

            // Get bytes from buffer
            for (int i = 0; i < numberOfItems; i++)
                items[i] = Get(i);

            // Return
            return items;
        }

        /// <summary>
        /// Returns latest byte from buffer
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        /// <example>
        /// CircularBuffer b = new CircularBuffer(10);
        /// b.Add(0x41);
        /// b.Add(0x42);
        /// b.Get(0); // This would be 0x42
        /// b.Get(1); // This would be 0x41
        /// </example>
        public T Get(int index)
        { 
            // Argument check
            if (index < 0)
                throw new ArgumentException("Index must be greater than or equal to zero");

            if (this.Length <= 0)
                throw new ArgumentException
                    (string.Format("Buffer size is {0}. Buffer needs to be greater than 0", this.Length));

            // Algorithm to find the buffer index
            index = index % this.Length;
            if (index < bufferIndex)
                index = bufferIndex - index - 1;
            else
                index = bufferIndex - index - 1 + this.Length;            

            // Return the item
            return buffer[index];
        }
    }
}

2 comments:

Japan Shah said...

why this is not thread safe?

Greg said...

Because "buffer" is not protected by a thread control structure. The title's implication is that instances of the class should not be shared between threads without further work