In this article, we will explore the Hashtable class in C#. We will comprehensively discuss its practical usage and key features.
Let’s dive in.
What Is a Hashtable in C#?
A Hashtable is a non-generic collection that represents items as key-value pairs stored in DictionaryEntry
objects.
The Hashtable
class implements the hash table data structure that uses a hash function to associate keys to their corresponding values. This collection can directly retrieve values associated with a specific key.
The Hashtable
class in C# is in the System.Collections
namespace and it has methods and properties that we can use to create, add, update, delete, and access items in it.
Prepare the Environment
To start, let’s define some building blocks in our HashtableInCSharp
project.
First, let’s create a custom User
class that serves as a model for storing user data in our application:
public class User { public string FirstName { get; set; } public string LastName { get; set; } public override string ToString() { return $"{FirstName} {LastName}"; } }
This article will use this class to generate values for populating our Hashtable
instances. We will map an integer key to each user data in the Hashtables. We override the ToString()
method to display more information about a User
object.
Also, let’s create a SharedData
class:
public static class SharedData { public const int InitialCapacity = 100; public static Dictionary<int, User> UserDictionary => new(5) { { 1, new User() { Id = 1, FirstName = "Rafa", LastName = "Lopez" } }, { 2, new User() { Id = 2, FirstName = "Michael", LastName = "Sam" } }, { 3, new User() { Id = 3, FirstName = "Sam", LastName = "Manalt" } }, { 4, new User() { Id = 4, FirstName = "Lone", LastName = "Costa" } }, { 5, new User() { Id = 5, FirstName = "Alberto", LastName = "Daci" } }, }; public static List<User> UserList => new(3) { new User() { Id = 6, FirstName = "Judit", LastName = "Peter" }, new User() { Id = 7, FirstName = "Steve", LastName = "Billing" }, new User() { Id = 8, FirstName = "Goddy", LastName = "Opara" }, }; }
This class provides shared data for testing our methods. It contains an initial capacity constant, a dictionary of users, and a list of users.
Finally, let’s define a PrintHashTableContent()
method in the Program
class:
public static void PrintHashTableContent(Hashtable userHashTable) { foreach (DictionaryEntry entry in userHashTable) { Console.WriteLine($"UserId:{entry.Key} UserName:{entry.Value}"); } }
We will use this method to display our users’ data from the Hashtable on the console.
Create a Hashtable in C#
In C#, there are over ten constructors that we can use to initialize a Hashtable
object. Let’s look at how to use three of those constructors to define a Hashtable.
Create an Empty Hashtable
First, let’s create an empty Hashtable
:
public static Hashtable CreateEmptyHashTable() => new();
Here, we define a Hashtable
object that uses the default initial capacity, comparer, load factor, and hashcode provider.
Create a Hashtable With an Initial Capacity
Next, let’s define a Hashtable by specifying an initial capacity:
public static Hashtable CreateHashTableWithInitialCapacity(int initialCapacity) => new(initialCapacity);
In this case, we create a Hashtable with an initial capacity. We use this constructor when we know the maximum number of items we will add to the Hashtable.
By setting the initial capacity of a Hashtable, we prevent the need for unnecessary resizing operations. This action enables us to conserve significant amounts of memory, as resizing entails the creation of a new internal array and rehashing all its elements. Moreover, when we set the initial capacity, we reduce memory overhead and enhance overall performance.
When we add items to a Hashtable beyond its capacity, it automatically expands in size to accommodate the additional elements.
Create a Hashtable From a Dictionary
Finally, let’s create a Hashtable from a dictionary:
public static Hashtable CreateHashTableFromDictionary(Dictionary<int, User> dictionary) => new(dictionary);
Here, we use the constructor that takes a dictionary as an argument to create a Hashtable. This constructor automatically populates the Hashtable with the key-value pairs from the dictionary. This approach proves useful when we have a dictionary that requires conversion to a Hashtable.
When we pass our UserDictionary
object from the SharedData
class to this method and invoke it in the Program
class, it creates a Hashtable that contains the following data:
UserId:5 UserName:Alberto Daci UserId:4 UserName:Lone Costa UserId:3 UserName:Sam Manalt UserId:2 UserName:Michael Sam UserId:1 UserName:Rafa Lopez
This Hashtable will serve as the data source for the remaining methods discussed in this article.
Add Elements to a HashTable in C#
Now, let’s demonstrate how we can add elements to our Hashtable:
public static Hashtable AddSampleDataToHashTable(Hashtable userHashTable, List<User> userList) { foreach (var user in userList) { userHashTable.Add(user.Id, user); } return userHashTable; }
In our AddSampleDataToHashTable()
method, we add users’ data to our Hashtable by calling the Add()
method in a foreach
loop.
We should note that a Hashtable does not guarantee any specific order for the elements we store in it. However, when we create a new hashtable using a source IDictionary
object, the new Hashtable arranges its elements in the same order as the enumerator iterates through the object.
Once we call this AddSampleDataToHashTable()
method by passing the UserList
from the SharedData
class, our Hashtable will have the following content:
UserId:8 UserName:Goddy Opara UserId:7 UserName:Steve Billing UserId:6 UserName:Judit Peter UserId:5 UserName:Alberto Daci UserId:4 UserName:Lone Costa UserId:3 UserName:Sam Manalt UserId:2 UserName:Michael Sam UserId:1 UserName:Rafa Lopez
It’s important to note that the keys within the Hashtable
must be unique to avoid exceptions. Also, while a key cannot be null, a value can be.
Retrieve Elements From a Hashtable in C#
Next up, let’s retrieve a particular user from our Hashtable:
public static User RetrieveElementFromHashTable(Hashtable userHashTable, int id) { if (userHashTable.ContainsKey(id)) { return (User)userHashTable[id]; } return default; }
Here, we call the ContainsKey()
method to check if the Hashtable contains the specified id
. If it does, we retrieve the corresponding value using the key.
In this scenario, we assume that the value mapped to the key id
is of type User
and thus perform a cast to the User
type. It is important to verify that the key exists in the Hashtable before accessing it to avoid potential exceptions.
Furthermore, we can retrieve all the elements in a Hashtable by iterating through it:
public static IList<User> RetrieveAllElementsFromHashTable(Hashtable userHashTable) { var userList = new List<User>(userHashTable.Count); foreach (DictionaryEntry entry in userHashTable) { userList.Add((User)entry.Value); } return userList; }
We first create a list of User
objects. Then, we use a DictionaryEntry
object and a foreach
loop to iterate through each key-value pair in the Hashtable. For each entry, we retrieve its value using entry.Value
and add it to our list of users.
Update An Element in a Hashtable in C#
To update an element in a Hashtable in C#, we can utilize the indexer feature:
public static Hashtable UpdateElementInHashTable(Hashtable userHashTable, int id) { if (userHashTable.ContainsKey(id)) { userHashTable[id] = new User() { FirstName = "Henry", LastName = "Stafford" }; return userHashTable; } return userHashTable; }
In this method, we first check if a particular user id exists in our Hashtable. If we find the id, we modify the user’s information associated with the specified id
in our Hashtable.
In our Program
class, once we call the UpdateElementInHashTable()
method with an id
value of 2, we get an updated Hashtable:
UserId:8 UserName:Goddy Opara UserId:7 UserName:Steve Billing UserId:6 UserName:Judit Peter UserId:5 UserName:Alberto Daci UserId:4 UserName:Lone Costa UserId:3 UserName:Sam Manalt UserId:2 UserName:Henry Stafford UserId:1 UserName:Rafa Lopez
As we can see, we have updated the user with id 2.
Remove Elements From a Hashtable in C#
Now, let’s see how we can remove an element from our Hashtable:
public static Hashtable RemoveElementFromHashTable(Hashtable userHashTable, int id) { userHashTable.Remove(id); return userHashTable; }
Here, we call the Remove()
method and provide the key(id) of the user we want to remove.
That said when we invoke the RemoveElementFromHashTable()
method with an id
value of 5 in the Program
class, our Hashtable becomes:
UserId:8 UserName:Goddy Opara UserId:7 UserName:Steve Billing UserId:6 UserName:Judit Peter UserId:4 UserName:Lone Costa UserId:3 UserName:Sam Manalt UserId:2 UserName:Henry Stafford UserId:1 UserName:Rafa Lopez
As we can see, we have removed the user with the id 5.
Sometimes, we may need to dispose of all the items stored in our Hashtable. To accomplish this, we can call the Clear()
method:
public static Hashtable RemoveAllElementsFromHashTable(Hashtable userHashTable) { userHashTable.Clear(); return userHashTable; }
This method removes all the items from our Hashtable and sets its count to zero.
Other Methods of the Hashtable Class
In this section, let’s discuss two other methods in the Hashtable
class that may be useful when developing our applications.
Clone()
The Clone()
method creates a shallow copy of our Hashtable:
public static Hashtable CloneHashTable(Hashtable hashTable) { return (Hashtable)hashTable.Clone(); }
A shallow copy of the Hashtable includes all the elements from the original Hashtable, encompassing both reference and value types. However, when it comes to reference types, the shallow copy merely stores a reference to the object. It does not create duplicate copies of the actual objects that the reference points to.
Synchronized()
We call the Synchronized()
method to create a thread-safe wrapper for our Hashtable:
public static Hashtable SynchronizeHashtable(Hashtable hashTable) { return Hashtable.Synchronized(hashTable); }
The wrapper we generate by invoking this method enables concurrent access for multiple users to read and write data from the collection. This function ensures thread safety. However, this wrapper restricts the write operations to only one writer at a time.
Visit here to see all the methods from the Hashtable
class.
Advantages of Using a Hashtable in C#
Using a Hashtable in C# offers several advantages.
Firstly, the Hashtable data structure facilitates rapid access to elements, making it well-suited for tasks such as caching or indexing.
Secondly, the utilization of key-value pairs in a Hashtable enables efficient storage of values alongside their corresponding keys. This allows us to easily retrieve values by specifying their unique key.
Also, the Hashtable.Synchronized()
method enables us to employ the Hashtable in a multithreaded environment safely. With its usage, we do not need to worry about race conditions and other problems that can occur in concurrent execution.
Additionally, when we use a Hashtable, its size dynamically adjusts as we add or remove items. This eliminates the need for manual resizing or specification of an initial capacity. However, if we know the initial capacity, specifying it can reduce the frequency of resizing operations.
Finally, the Hashtable class incorporates an enumeration feature by implementing the IEnumerable
interface. This permits the use of a foreach
loop to access the data stored within the Hashtable, facilitating various operations such as updating and searching.
Disadvantages of Using a Hashtable in C#
There are some disadvantages associated with using the Hashtable class in our programs.
To start with, Hashtables do not maintain a sorted arrangement of elements. Therefore, alternative data structures such as SortedList or SortedDictionary<TKey, TValue>
might be more suitable if we require an ordered collection.
Also, Hashtables are error-prone due to their lack of typing. This requires frequent casting between objects and the expected type, increasing the likelihood of errors. Since the compiler cannot verify the consistency of types, it becomes easier for us to place the wrong type into the wrong collection.
Finally, Hashtables are less performant than their generic alternative, the Dictionary<TKey, TValue> class. Generic collections have the advantage of avoiding the need to box value types as objects. For example, a Dictionary<int, string>
will store its keys as integers and its values as strings. This is more efficient than storing all the keys and values as objects since it eliminates the need for boxing.
Also, it’s worth mentioning that Microsoft recommends using the generic Dictionary<TKey, TValue> class for new development. The Hashtable class is a non-generic collection, and Microsoft advises against using non-generic collections. To learn more about the drawbacks of using the Hashtable class and other non-generic collections, please visit the Microsoft documentation on “Non-generic collections should not be used“.
Conclusion
In this article, we have learned that the Hashtable
is a non-generic collection that we can use to store items as key-value pairs in our applications. One notable advantage of the Hashtable
class is its ability to facilitate quick retrieval of elements. However, it’s worth noting that a Hashtable lacks inherent ordering of its items. Furthermore, the absence of typing in a Hashtable makes it susceptible to errors. Finally, Hashtables exhibit inferior performance when compared to dictionaries.