In this article, we are going to study Stack in C#, a collection of objects implementing the last-in-first-out method.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

What is Stack in C#?

Stack in C# is one of the many collections available to us in C#. We use Stack when we want the last added element to be the first one to go.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Let’s imagine having to stack some dining plates and then having to set them on a table. The dining plate on the top (the last added to the stack) will be removed first, while the one on the bottom will remain on the stack for longer.

A practical implementation in the software would be an undo-redo operation in a common editor, where we can use a stack to store all the performed operations by a user and then use it to retrieve them from the most recently performed action to the oldest one.

In summary, we use Stack in C# when we need to store data that respect the LIFO (Last-In-First-Out) method. 

LIFO is different from FIFO. If you’re interested in the First-In-First-Out method, check out our Queue in C# article.

We can create a stack in C# using the Stack class. When we add an item to a stack we say that we are pushing the item, while when we remove it we are popping the item.

When we initialize a Stack, it has a capacity that indicates the number of elements it can store. We will see that it is increased as we add more items to Stack. The Count property, on the other hand, tells us how many elements are effectively contained in Stack.

Generic and Non-Generic Collections

C# includes the generic version Stack<T> as well, which is more commonly used than the non-generic one. If we opt-in for the non-generic Stack, we can add elements of different types (a string, an integer, etc for example).

Since we don’t know the type of all the items during the compile time, the compiler needs to perform the cast between object and the actual type of every item, and this affects the performance.

The generic version, instead, permits us to specify the same type <T> for all the items we are going to add to the collection. Stack<T> collection is in the System.Collections.Generic namespace.

Stack in C# Constructors

Both the non-generic and the generic versions of Stack have three constructors.

Let’s start with the non-generic case:

Stack firstNonGenericStack = new Stack();

It initializes an empty stack, having the default initial capacity. For the non-generic Stack class, the default initial capacity is ten. Invoking firstNonGenericStack.Count we will obtain zero since it doesn’t contain any element yet.

The second constructor is Stack(ICollection):

List<string> letters = new List<string>(){ "a", "b", "c" };
Stack secondNonGenericStack = new Stack(letters);

This constructor creates a Stack starting from a collection derived from ICollection the interface. In this case, we created a stack with the elements contained in the list letters. The number of copied elements (three in this case) is equal to the initial capacity of Stack, and it is also equal to the value obtained by calling lettersStack.Count.

The last constructor is Stack(Int32), where the integer represents the initial capacity of Stack:

Stack thirdNonGenericStack = new Stack(5);

Here, we are creating a stack specifying the initial capacity of five.

Let’s see what happens with the generic counterparts of the constructors:

Stack<int> firstGenericStack = new Stack<int>();

We created a Stack that can store only int items, having a default initial capacity. For the generic stack, the default initial capacity is zero. 

The second constructor takes an IEnumerable<T> as input, and it creates a stack starting from this collection:

List<string> letters = new List<string>(){ "a", "b", "c" }; 
Stack<string> secondGenericStack = new Stack<string>(letters);

In this case, we created secondGenericStack containing the elements from the letters list.

Finally, the third constructor for the generic case is Stack<T>(Int32):

Stack<int> thirdGenericStack = new Stack<int>(5);

It is a stack that contains only integer values with a capacity of five.

Stack in C# Properties and Methods

We can now explore some of the most commonly used properties and methods of Stack in C#. 

Count

The Count property returns the number of elements stored in Stack:

List<string> letters = new List<string>() { "a", "b", "c" };
Stack lettersStack = new Stack(letters);

Console.WriteLine($"lettersStack contains {lettersStack.Count} items\n"); //lettersStack contains 3 items

Push()

We can add an item to Stack by using the Push method. For a non-generic Stack, like lettersStack, a push operation is allowed for any item of a type derived by object?.

So let’s define a class Page we’re going to work with:

public class Page
{
    public Page(string title, DateTime lastUpdateTime)
    {
        Title = title;
        LastUpdateTime = lastUpdateTime;
    }

    public string Title { get; set; }

    public DateTime LastUpdateTime { get; set; }
}

We can add a Page to the non-generic lettersStack:

Page page = new Page("New page", DateTime.now);

Console.WriteLine($"lettersStack contains {lettersStack.Count} items"); //lettersStack contains 3 items
lettersStack.Push(page);
lettersStack.Push(1);
lettersStack.Push("hello");

Console.WriteLine($"lettersStack contains {lettersStack.Count} items");//lettersStack contains 6 items

The number of items contained in lettersStack increases as we push new elements.

Since in the generic case Stack<T> handles the elements of the same type, we can define a pageStack stack which can handle Page items and then push two pages into it:

Stack<Page> pageStack = new Stack<Page>();

var page1 = new Page("The first visited page", DateTime.Now);
var page2 = new Page("The second visited page", DateTime.Now);

pageStack.Push(page1);
pageStack.Push(page2);

For both the generic and non-generic cases, when the number of elements stored in Stack reaches its capacity, then the capacity of Stack is doubled. This is the default behavior of Stack.

Peek()

The Peek() method returns the last added element, but it doesn’t modify the stack:

object? lastAddedItem = lettersStack.Peek(); 
var lastVisitedPage = pageStack.Peek();

Console.WriteLine($"the first retrieved element in lettersStack is {lastAddedItem}");
Console.WriteLine($"the first retrieved element in pageStack is {lastVisitedPage.Title}");
Console.WriteLine($"lettersStack contains {lettersStack.Count} items");
Console.WriteLine($"pageStack contains {pageStack.Count} items\n");

In both cases, the Count property doesn’t change:

the first retrieved element in lettersStack is hello
the first retrieved element in pageStack is The second visited page
lettersStack contains 6 items
pageStack contains 2 items

If we call Peek() method on an empty Stack, it throws a System.InvalidOperationException: Stack empty exception. For the generic Stack (pageStack), we can use the TryPeek(out T result) method, which checks if Stack is empty and if there are any items to peek at.

If there’s an object in Stack, it returns true and the object as a out value, false otherwise:

var result = pageStack.TryPeek(out topPage);

Console.WriteLine($"TryPeek returns: {result}");
Console.WriteLine($"The title of the topPage in pageStack is: {topPage?.Title}");
Console.WriteLine($"pageStack contains {pageStack.Count} items\n");

The output parameter is the page on the top of Stack:

TryPeek returns: True
The title of the topPage in pageStack is: The second visited page
pageStack contains 2 items

Pop()

The Pop() method removes and returns the last element added to Stack, and it is equivalent for both the non-generic and generic cases:

object? lastAddedItem = lettersStack.Pop();
var lastVisitedPage = pageStack.Pop();

Console.WriteLine($"The first retrieved element in lettersStack is {lastAddedItem}");
Console.WriteLine($"The first retrieved element in pageStack is {lastVisitedPage.Title}");
Console.WriteLine($"lettersStack contains {lettersStack.Count} items");
Console.WriteLine($"pageStack contains {pageStack.Count} items\n");

In this case, lastAddedItem is hello and lastVisitedPage is "The second visited page", since they are the last added elements to lettersStack and pageStack respectively.

Both lettersStack and pageStack won’t contain anymore these elements, since we removed them using Pop():

The first retrieved element in lettersStack is hello
The first retrieved element in pageStack is The second visited page
lettersStack contains 5 items
pageStack contains 1 items

If we call Pop() method to an empty Stack, it will throw a System.InvalidOperationException: Stack empty exception. To avoid this issue, for the generic stack (pageStack) there is the TryPop(out T result) method, returning true if there is an object on the top of Stack, false otherwise:

bool result = pageStack.TryPop(out var topPage);

Console.WriteLine($"TryPop returns: {result}");
Console.WriteLine($"the topPage in pageStack is: {topPage?.Title}");
Console.WriteLine($"pageStack contains {pageStack.Count} items\n");

This will print out:

TryPop returns:  True
the title of the topPage in pageStack is: The first visited page
pageStack contains 1 items

Clear()

The Clear() method removes all the elements from a stack. After this operation, Stack will be empty:

lettersStack.Clear();

Console.WriteLine($"lettersStack contains {lettersStack.Count} items\n");//lettersStack contains 0 items

As we expect, lettersStack contains 0 items:

Stack Thread Safety

Let’s see how Stack deals with thread safety in C#.

Non-Generic Case

Sometimes, we need to access the same stack from multiple threads and this can lead to unexpected behaviors. To avoid these problems, we can use the Synchronized wrapper, used to return a thread-safe wrapper for Stack:

Stack synchronizedStack = Stack.Synchronized(lettersStack);

Stack.Synchronized takes as input the non-generic Stack and returns a wrapper around it. Now, if we call the IsSynchronized property (that checks if the access to Stack is thread-safe),  for both:

Console.WriteLine($"lettersStack is thread-safe: {lettersStack.IsSynchronized}"); //False
Console.WriteLine($"synchronizedStack is thread-safe: {synchronizedStack.IsSynchronized}"); //True

Generic Case

For the generic stack, we should use ConcurrentStack<T>:

ConcurrentStack<Page> threadSafePageStack = new ConcurrentStack<Page>();

As with the non-generic case, the ConcurrentStack<T> class is a wrapper around Stack<T>, providing thread safety, and using locking to synchronize different threads internally.

Conclusion

In this article, we’ve learned about both the non-generic and the generic versions of Stack in C#. We’ve shown how to add and remove elements to stacks, and how to implement a thread-safe stack if we need to.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!