.NET Dictionaries
Introduction
Dictionaries are one of the most powerful and commonly used collection types in .NET programming. A dictionary is a collection that stores data in key-value pairs, allowing you to quickly retrieve values using their associated keys. Unlike arrays or lists where you access elements by their numeric index, dictionaries let you use custom keys (like strings, integers, or even custom objects) to access your data.
In this tutorial, we'll explore the Dictionary<TKey, TValue>
class, which is part of the System.Collections.Generic
namespace in .NET. We'll learn how to create dictionaries, add and remove items, search for specific entries, and apply dictionaries to solve real-world programming problems.
Dictionary Basics
What is a Dictionary?
A dictionary in .NET is a generic collection that provides a mapping from keys to values. Each key in the dictionary must be unique, and each key maps to exactly one value. The key is used to retrieve its corresponding value, making lookups very efficient.
Creating a Dictionary
Let's start by creating a simple dictionary. Here's how you can create a dictionary that maps string keys to integer values:
using System;
using System.Collections.Generic;
Dictionary<string, int> ages = new Dictionary<string, int>();
You can also initialize a dictionary with values using collection initializers:
Dictionary<string, int> ages = new Dictionary<string, int>()
{
{ "John", 28 },
{ "Mary", 26 },
{ "Bob", 31 }
};
// Alternative syntax using key-value pairs
Dictionary<string, int> scores = new Dictionary<string, int>()
{
["Alice"] = 95,
["David"] = 87,
["Emma"] = 92
};
Basic Dictionary Operations
Adding Elements
You can add elements to a dictionary using the Add
method or the indexer syntax:
Dictionary<string, int> fruitInventory = new Dictionary<string, int>();
// Using the Add method
fruitInventory.Add("Apple", 100);
fruitInventory.Add("Banana", 80);
// Using indexer syntax
fruitInventory["Orange"] = 60;
fruitInventory["Grape"] = 45;
// Output the count
Console.WriteLine($"Fruit inventory contains {fruitInventory.Count} items.");
// Output: Fruit inventory contains 4 items.
Accessing Values
You can access values in a dictionary using the key:
Dictionary<string, int> fruitInventory = new Dictionary<string, int>()
{
{ "Apple", 100 },
{ "Banana", 80 },
{ "Orange", 60 },
{ "Grape", 45 }
};
// Access using key
int appleCount = fruitInventory["Apple"];
Console.WriteLine($"We have {appleCount} apples in stock.");
// Output: We have 100 apples in stock.
Checking if a Key Exists
Trying to access a key that doesn't exist will throw a KeyNotFoundException
. To avoid this, you should check if a key exists before accessing it:
Dictionary<string, int> fruitInventory = new Dictionary<string, int>()
{
{ "Apple", 100 },
{ "Banana", 80 }
};
string fruit = "Mango";
// Check if key exists
if (fruitInventory.ContainsKey(fruit))
{
Console.WriteLine($"We have {fruitInventory[fruit]} {fruit}s in stock.");
}
else
{
Console.WriteLine($"Sorry, we don't have {fruit} in stock.");
}
// Output: Sorry, we don't have Mango in stock.
Alternatively, you can use the TryGetValue
method, which is more efficient as it checks for existence and retrieves the value in a single operation:
string fruit = "Banana";
if (fruitInventory.TryGetValue(fruit, out int count))
{
Console.WriteLine($"We have {count} {fruit}s in stock.");
}
else
{
Console.WriteLine($"Sorry, we don't have {fruit} in stock.");
}
// Output: We have 80 Bananas in stock.
Updating Values
You can update values in a dictionary using the indexer syntax:
Dictionary<string, int> fruitInventory = new Dictionary<string, int>()
{
{ "Apple", 100 },
{ "Banana", 80 }
};
// Update value
fruitInventory["Apple"] = 75;
Console.WriteLine($"Updated apple inventory: {fruitInventory["Apple"]}");
// Output: Updated apple inventory: 75
Removing Elements
You can remove elements from a dictionary using the Remove
method:
Dictionary<string, int> fruitInventory = new Dictionary<string, int>()
{
{ "Apple", 100 },
{ "Banana", 80 },
{ "Orange", 60 }
};
// Remove element
fruitInventory.Remove("Banana");
Console.WriteLine($"Fruit inventory now contains {fruitInventory.Count} items.");
// Output: Fruit inventory now contains 2 items.
Iterating Through a Dictionary
You can iterate through a dictionary using a foreach
loop:
Dictionary<string, int> fruitInventory = new Dictionary<string, int>()
{
{ "Apple", 100 },
{ "Banana", 80 },
{ "Orange", 60 }
};
// Iterate through dictionary
Console.WriteLine("Current fruit inventory:");
foreach (KeyValuePair<string, int> item in fruitInventory)
{
Console.WriteLine($"- {item.Key}: {item.Value}");
}
/* Output:
Current fruit inventory:
- Apple: 100
- Banana: 80
- Orange: 60
*/
// You can also iterate through just keys or values
Console.WriteLine("\nFruit names in inventory:");
foreach (string fruit in fruitInventory.Keys)
{
Console.WriteLine($"- {fruit}");
}
Console.WriteLine("\nStock quantities:");
foreach (int quantity in fruitInventory.Values)
{
Console.WriteLine($"- {quantity}");
}
Advanced Dictionary Features
Dictionary with Complex Types
Dictionaries can use complex types as keys or values. Here's an example with a custom class as the value:
using System;
using System.Collections.Generic;
public class Employee
{
public string Name { get; set; }
public string Department { get; set; }
public double Salary { get; set; }
public override string ToString()
{
return $"{Name} ({Department}): ${Salary:F2}";
}
}
// In Main method or other code:
Dictionary<int, Employee> employees = new Dictionary<int, Employee>()
{
{ 1001, new Employee { Name = "John Smith", Department = "IT", Salary = 75000 } },
{ 1002, new Employee { Name = "Sarah Johnson", Department = "HR", Salary = 65000 } },
{ 1003, new Employee { Name = "Michael Brown", Department = "Finance", Salary = 80000 } }
};
// Accessing an employee
Employee emp = employees[1002];
Console.WriteLine($"Employee #1002: {emp}");
// Output: Employee #1002: Sarah Johnson (HR): $65000.00
// Iterate through all employees
foreach (var pair in employees)
{
Console.WriteLine($"ID: {pair.Key}, Employee: {pair.Value}");
}
Using Custom Types as Dictionary Keys
When using custom types as dictionary keys, you need to ensure that they properly implement Equals()
and GetHashCode()
methods. Let's see an example:
using System;
using System.Collections.Generic;
public class ProductKey
{
public string Category { get; set; }
public string Name { get; set; }
// Override Equals for correct comparison
public override bool Equals(object obj)
{
if (obj is not ProductKey other)
return false;
return Category == other.Category && Name == other.Name;
}
// Override GetHashCode for efficient dictionary lookup
public override int GetHashCode()
{
return HashCode.Combine(Category, Name);
}
public override string ToString()
{
return $"{Category}: {Name}";
}
}
// In Main method or other code:
Dictionary<ProductKey, decimal> products = new Dictionary<ProductKey, decimal>()
{
{ new ProductKey { Category = "Electronics", Name = "Laptop" }, 1299.99m },
{ new ProductKey { Category = "Electronics", Name = "Smartphone" }, 799.99m },
{ new ProductKey { Category = "Furniture", Name = "Desk" }, 249.95m }
};
// Lookup a product
var key = new ProductKey { Category = "Electronics", Name = "Laptop" };
if (products.TryGetValue(key, out decimal price))
{
Console.WriteLine($"The price of {key} is ${price}");
}
// Output: The price of Electronics: Laptop is $1299.99
Practical Applications of Dictionaries
Example 1: Word Frequency Counter
Let's build a simple word frequency counter using a dictionary:
using System;
using System.Collections.Generic;
string text = "The quick brown fox jumps over the lazy dog. The dog barks at the fox.";
// Convert to lowercase and split by spaces and punctuation
string[] words = text.ToLower().Split(new char[] { ' ', '.', ',', '!', '?', ';', ':' },
StringSplitOptions.RemoveEmptyEntries);
// Create a dictionary to store word frequencies
Dictionary<string, int> wordFrequency = new Dictionary<string, int>();
// Count word occurrences
foreach (string word in words)
{
if (wordFrequency.ContainsKey(word))
{
wordFrequency[word]++;
}
else
{
wordFrequency[word] = 1;
}
}
// Display results
Console.WriteLine("Word frequency analysis:");
foreach (var item in wordFrequency)
{
Console.WriteLine($"'{item.Key}': {item.Value} occurrence(s)");
}
/* Output:
Word frequency analysis:
'the': 4 occurrence(s)
'quick': 1 occurrence(s)
'brown': 1 occurrence(s)
'fox': 2 occurrence(s)
'jumps': 1 occurrence(s)
'over': 1 occurrence(s)
'lazy': 1 occurrence(s)
'dog': 2 occurrence(s)
'barks': 1 occurrence(s)
'at': 1 occurrence(s)
*/
Example 2: Student Grade Tracker
Here's a more practical example that uses a dictionary to track student grades:
using System;
using System.Collections.Generic;
using System.Linq;
// Create dictionary to store student grades
Dictionary<string, List<int>> studentGrades = new Dictionary<string, List<int>>()
{
{ "Alice", new List<int> { 85, 92, 78, 95 } },
{ "Bob", new List<int> { 70, 65, 72, 68 } },
{ "Charlie", new List<int> { 90, 87, 93, 89 } },
{ "Diana", new List<int> { 82, 88, 79, 84 } }
};
// Calculate and display averages
Console.WriteLine("Student Grade Averages:");
foreach (var student in studentGrades)
{
double average = student.Value.Average();
string letterGrade = GetLetterGrade(average);
Console.WriteLine($"{student.Key}: {average:F1} ({letterGrade})");
}
// Add a new student
studentGrades["Edward"] = new List<int> { 77, 81, 75, 80 };
// Add a new grade for an existing student
studentGrades["Alice"].Add(88);
Console.WriteLine($"\nAlice's updated grades: {string.Join(", ", studentGrades["Alice"])}");
Console.WriteLine($"Alice's new average: {studentGrades["Alice"].Average():F1}");
// Helper method to convert numeric grade to letter grade
static string GetLetterGrade(double grade)
{
return grade switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "F"
};
}
/* Output:
Student Grade Averages:
Alice: 87.5 (B)
Bob: 68.8 (F)
Charlie: 89.8 (B)
Diana: 83.2 (B)
Alice's updated grades: 85, 92, 78, 95, 88
Alice's new average: 87.6
*/
Performance Considerations
Dictionaries in .NET are implemented as hash tables, providing very fast lookups, insertions, and deletions with an average time complexity of O(1) for these operations. However, there are some performance considerations to keep in mind:
- Hash Collisions: If multiple keys produce the same hash code, performance can degrade due to collisions.
- Initial Capacity: If you know approximately how many elements you'll store, you can optimize by setting an initial capacity:
csharp
Dictionary<string, int> dict = new Dictionary<string, int>(10000);
- Memory Usage: Dictionaries consume more memory than simple arrays or lists due to their hash table structure.
- Key Type: Using simple types like strings, integers, or structs as keys typically performs better than using complex objects.
Summary
Dictionaries are a versatile and efficient way to store and retrieve data using key-value pairs in .NET applications. They offer:
- Fast lookups, insertions, and deletions
- Flexible key and value types
- Convenient methods for checking key existence, iteration, and manipulation
- Great performance for scenarios requiring quick access to values based on arbitrary keys
We've covered the basics of creating and using dictionaries, advanced features like using custom types as keys, and practical applications that demonstrate how dictionaries can solve real-world programming problems.
Additional Resources
- Microsoft Learn -
Dictionary<TKey,TValue>
Class - C# Documentation - Dictionary Collection
- Dictionary vs Hashtable in .NET
Exercises
-
Create a dictionary that maps country names to their capital cities. Add at least 10 countries, then write code to let a user look up a capital by entering a country name.
-
Implement a simple phone book application that allows adding contacts (name and phone number), looking up numbers by name, and displaying all contacts.
-
Build a simple inventory management system using a dictionary where the keys are product IDs and values are objects containing product name, quantity, and price.
-
Create a word counter that reads a text file, counts the frequency of each word, and displays the top 10 most common words.
-
Implement a cache using a dictionary to store the results of expensive calculations, checking the cache before computing a value.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)