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)