In C#, dictionaries rely on keys behaving correctly when it comes to equality and hashing. Standard types like string
, int
, and float
work seamlessly as dictionary keys because Microsoft has ensured they implement proper equality comparison and hashing. However, when using a custom type as a key, you need to manually define how equality and hashing should work.
In this article, we will:
- Create a custom type (
CountryCode
) and use it as a dictionary key. - Understand why overriding Equals and GetHashCode is necessary.
- Implement a console application that demonstrates these concepts.
1. Creating a Custom Type as a Dictionary Key
Let's assume we want to store country details in a dictionary where the key is a custom type (CountryCode
) instead of a string. The CountryCode
type will wrap a string representing a country's ISO code (e.g., "USA"
, "FRA"
, "DZA"
).
Defining the CountryCode Class
using System; using System.Collections.Generic; public class CountryCode { public string Code { get; } public CountryCode(string code) { if (string.IsNullOrWhiteSpace(code) || code.Length != 3) { throw new ArgumentException("Country code must be exactly 3 characters.", nameof(code)); } Code = code.ToUpper(); // Normalize case } public override string ToString() => Code; // Override Equals for value comparison public override bool Equals(object obj) { if (obj is CountryCode other) { return string.Equals(this.Code, other.Code, StringComparison.OrdinalIgnoreCase); } return false; } // Override GetHashCode to match Equals behavior public override int GetHashCode() { return StringComparer.OrdinalIgnoreCase.GetHashCode(Code); } // Overload == and != for consistency public static bool operator ==(CountryCode left, CountryCode right) { if (left is null) return right is null; return left.Equals(right); } public static bool operator !=(CountryCode left, CountryCode right) { return !(left == right); } }
Key Takeaways:
- Constructor validation: Ensures the country code is always 3 characters long.
- Overriding
ToString()
: MakesCountryCode
display its value properly. - Overriding
Equals()
: Compares values instead of object references. - Overriding
GetHashCode()
: Ensures correct dictionary behavior. - Overloading
==
and!=
: Makes equality checks consistent.
2. Using CountryCode as a Dictionary Key
Now, let's define a dictionary that maps country codes to country names.
Defining the Country Class
public class Country { public string Name { get; } public CountryCode Code { get; } public Country(string name, string code) { Name = name; Code = new CountryCode(code); } public override string ToString() => $"{Name} ({Code})"; }
This class represents a country with a name and an ISO country code.
3. Implementing a Dictionary with Custom Keys
Now, we will create a dictionary where the key is CountryCode
and the value is a Country
object.
Building the Dictionary
class Program { static void Main() { // Dictionary with CountryCode as the key Dictionary<CountryCode, Country> countries = new Dictionary<CountryCode, Country> { { new CountryCode("USA"), new Country("United States", "USA") }, { new CountryCode("FRA"), new Country("France", "FRA") }, { new CountryCode("DZA"), new Country("Algeria", "DZA") } }; Console.Write("Enter a country code: "); string userInput = Console.ReadLine(); // Convert user input to CountryCode CountryCode inputCode = new CountryCode(userInput); // Lookup country if (countries.TryGetValue(inputCode, out Country foundCountry)) { Console.WriteLine($"Country found: {foundCountry}"); } else { Console.WriteLine("Country not found."); } } }
4. Testing the Application
Example Run:
Enter a country code: dza Country found: Algeria (DZA)
This works even though we entered "dza"
in lowercase because our Equals
method ignores case.
What Happens if We Don't Override Equals
and GetHashCode
?
If we remove these overrides, the dictionary lookup will fail. The dictionary won't recognize "dza"
and "DZA"
as the same key, even though they contain the same value.
5. Summary
-
Why Use a Custom Type as a Key?
- Provides better control over data validation and formatting.
- Helps prevent accidental mistakes (e.g., using
"USA "
instead of"USA"
).
-
What Issues Can Occur?
- Dictionaries compare keys using
Equals()
andGetHashCode()
. - Reference types compare object instances by default, not values.
- Dictionaries compare keys using
-
How to Fix These Issues?
- Override
Equals()
to compare values. - Override
GetHashCode()
to ensure hashing is consistent. - Use a case-insensitive
StringComparer
to handle input variations. - Overload
==
and!=
to ensure equality checks work properly.
- Override
By following these steps, we ensure that our dictionary functions correctly even when using custom types as keys.
Top comments (0)