Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing Undo/Redo Functionality with the Generic Stack in C#

Tech 1

The .NET framework includes various generic collections, such as Stack<T> for last-in-first-out operations, Queue<T> for first-in-first-out, List<T> for ordered and indexed elements, LinkedList<T> for doubly linked lists without indexing, and ISet<T> implementations like HashSet<T> (unordered) and SortedSet<T> (ordered). IDictionary<TKey, TValue> provides key-value pairs, with SortedList<TKey, TValue> maintaining order and supporting indexing by key or value.

Stack<T> offers methods like Push, Pop, Peek, and Count. It operates on a last-in-first-out principle, similar to stacking plates. Example usage:

class Program
{
    static void Main(string[] args)
    {
        var personA = new Person { Id = 1, FullName = "Alice", Sex = "Female" };
        var personB = new Person { Id = 2, FullName = "Bob", Sex = "Male" };
        Stack<Person> personStack = new Stack<Person>();
        personStack.Push(personA);
        personStack.Push(personB);
        Person topPerson = personStack.Peek();
        Console.WriteLine("Top element: " + topPerson.FullName);
        foreach (var person in personStack)
        {
            Console.WriteLine($"ID: {person.Id}, Name: {person.FullName}");
        }
        Person poppedPerson = personStack.Pop();
        Console.WriteLine("Popped: " + poppedPerson.FullName);
        Console.WriteLine("Current count: " + personStack.Count);
        Console.ReadKey();
    }
}
public class Person
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public string Sex { get; set; }
}

A custom generic stack can be implemented using an aray and an index pointer. Push increments the index and assigns the element, with capacity expansion if needed. Pop returns the top element, resets it to default, and decrements the index. Peek retrieves the top element without removal.

public class CustomStack<T>
{
    private T[] items;
    private int topIndex = -1;
    private int currentCapacity;
    public CustomStack(int initialCapacity = 5)
    {
        currentCapacity = initialCapacity;
        items = new T[currentCapacity];
    }
    public int Size => topIndex + 1;
    public void Add(T item)
    {
        if (Size == currentCapacity)
            ExpandCapacity();
        topIndex++;
        items[topIndex] = item;
    }
    public T Remove()
    {
        if (Size < 1)
            throw new InvalidOperationException("Stack is empty");
        T item = items[topIndex];
        items[topIndex] = default(T);
        topIndex--;
        return item;
    }
    public T ViewTop()
    {
        if (Size < 1)
            throw new InvalidOperationException("Stack is empty");
        return items[topIndex];
    }
    private void ExpandCapacity()
    {
        currentCapacity = (currentCapacity + 1) * 2;
        T[] newItems = new T[currentCapacity];
        Array.Copy(items, newItems, items.Length);
        items = newItems;
    }
}

Client code for testing:

static void Main(string[] args)
{
    CustomStack<double> customStack = new CustomStack<double>();
    for (int j = 0; j < 8; j++)
    {
        Console.WriteLine($"Adding {j} to stack");
        customStack.Add(j);
        Console.WriteLine("Stack size: " + customStack.Size);
    }
    for (int j = 0; j < 8; j++)
    {
        Console.WriteLine($"Top element: {customStack.ViewTop()}");
        customStack.Remove();
        Console.WriteLine("Stack size: " + customStack.Size);
    }
    try
    {
        customStack.ViewTop();
    }
    catch (InvalidOperationException e)
    {
        Console.WriteLine(e.Message);
    }
    try
    {
        customStack.Remove();
    }
    catch (InvalidOperationException e)
    {
        Console.WriteLine(e.Message);
    }
    Console.ReadKey();
}

To implement undo/redo functionality, define an interface for commands:

public interface IOperation<T>
{
    T Execute(T data);
    T Reverse(T data);
}

Implement a command for integer addition:

public class IncrementCommand : IOperation<int>
{
    private int incrementAmount;
    public IncrementCommand(int amount = 0)
    {
        incrementAmount = amount;
    }
    public int Execute(int data)
    {
        return data + incrementAmount;
    }
    public int Reverse(int data)
    {
        return data - incrementAmount;
    }
}

Create a manager class using Stack<T> to handle undo and redo stacks:

public class CommandManager<T>
{
    private Stack<IOperation<T>> undoStack;
    private Stack<IOperation<T>> redoStack;
    public CommandManager()
    {
        Initialize();
    }
    public int UndoStackSize => undoStack.Count;
    public int RedoStackSize => redoStack.Count;
    private void Initialize()
    {
        undoStack = new Stack<IOperation<T>>();
        redoStack = new Stack<IOperation<T>>();
    }
    public T Perform(IOperation<T> operation, T data)
    {
        T result = operation.Execute(data);
        undoStack.Push(operation);
        redoStack.Clear();
        return result;
    }
    public T Undo(T data)
    {
        if (undoStack.Count > 0)
        {
            IOperation<T> operation = undoStack.Pop();
            T result = operation.Reverse(data);
            redoStack.Push(operation);
            return result;
        }
        return data;
    }
    public T Redo(T data)
    {
        if (redoStack.Count > 0)
        {
            IOperation<T> operation = redoStack.Pop();
            T result = operation.Execute(data);
            undoStack.Push(operation);
            return result;
        }
        return data;
    }
}

Example usage in a client:

static void Main(string[] args)
{
    CommandManager<int> manager = new CommandManager<int>();
    int total = 0;
    total = manager.Perform(new IncrementCommand(15), total);
    total = manager.Perform(new IncrementCommand(25), total);
    Console.WriteLine($"Initial total: {total}");
    total = manager.Undo(total);
    Console.WriteLine($"After undo: {total}");
    Console.ReadKey();
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.