Welcome to our exploration of sorted dictionaries using custom classes and comparers in C#. In today's lesson, we'll learn how to use custom classes as keys in sorted dictionaries. This approach enhances data organization and access. With the addition of comparers, we can dictate the order in such dictionaries.
A sorted dictionary is a dictionary with its keys always in order. This arrangement makes operations like searching for keys within a range more efficient. In C#, we use the SortedDictionary
class to create them:
C#1using System; 2using System.Collections.Generic; 3 4public class Program { 5 public static void Main(string[] args) { 6 SortedDictionary<string, int> sMap = new SortedDictionary<string, int>(); 7 sMap["a"] = 1; 8 sMap["b"] = 2; 9 sMap["c"] = 3; 10 11 foreach(var kvp in sMap) { 12 Console.WriteLine($"{kvp.Key}={kvp.Value}"); // Outputs "a=1", "b=2", "c=3" 13 } 14 } 15}
Custom classes enable us to create objects that fit our data — for instance, a Person
class for employee information or a Book
class for a library database. In C#, classes are the blueprints for creating objects.
Consider this simple class, for example:
C#1using System; 2 3public class Person { 4 public string Name { get; private set; } 5 public int Age { get; private set; } 6 7 public Person(string name, int age) { 8 Name = name; 9 Age = age; 10 } 11} 12 13public class Program { 14 public static void Main(string[] args) { 15 Person person = new Person("John Doe", 30); 16 Console.WriteLine(person.Name); // Outputs "John Doe" 17 Console.WriteLine(person.Age); // Outputs 30 18 } 19}
Using custom classes as dictionary keys helps organize complex multivariate keys in a sorted dictionary. Consider the following example using the Person
class as a key in a sorted dictionary (i.e., SortedDictionary
). However, this will not work yet.
C#1using System; 2using System.Collections.Generic; 3 4public class Person { 5 public string Name { get; private set; } 6 public int Age { get; private set; } 7 8 public Person(string name, int age) { 9 Name = name; 10 Age = age; 11 } 12 13 public override int GetHashCode() { 14 return HashCode.Combine(Name, Age); 15 } 16 17 // Rest of the class... 18} 19 20public class Program { 21 public static void Main(string[] args) { 22 SortedDictionary<Person, string> people = new SortedDictionary<Person, string>(); 23 24 Person john = new Person("John", 30); 25 Person alice = new Person("Alice", 25); 26 27 people[john] = "Programmer"; 28 people[alice] = "Designer"; 29 } 30}
We can see here that John is assigned the value "Programmer"
, and Alice is assigned the value "Designer."
However, this code will produce an exception. The reason is that the SortedDictionary
needs a way to compare the Person
objects to maintain its order. This requires not only implementing GetHashCode
and Equals
(important for identifying objects) but also providing a means to compare objects, typically through the IComparable<T>
interface or a custom IComparer<T>
.
C# uses a comparer to determine the order of two keys. To make this comparison, we add the IComparable
interface or a custom IComparer
to our class. Without these methods, SortedDictionary
can't compare its Person
class keys. Here’s how to modify the Person
class to implement the IComparable
interface:
C#1using System; 2using System.Collections.Generic; 3 4public class Person : IComparable<Person> { 5 public string Name { get; private set; } 6 public int Age { get; private set; } 7 8 public Person(string name, int age) { 9 Name = name; 10 Age = age; 11 } 12 13 public int CompareTo(Person other) { 14 int ageCompare = this.Age.CompareTo(other.Age); 15 if (ageCompare != 0) { 16 return ageCompare; 17 } 18 return this.Name.CompareTo(other.Name); 19 } 20 21 public override bool Equals(object obj) { 22 if (obj == null || GetType() != obj.GetType()) 23 return false; 24 25 Person person = (Person)obj; 26 return Age == person.Age && Name == person.Name; 27 } 28 29 public override int GetHashCode() { 30 return HashCode.Combine(Name, Age); 31 } 32} 33 34public class Program { 35 public static void Main(string[] args) { 36 SortedDictionary<Person, string> people = new SortedDictionary<Person, string>(); 37 38 Person john = new Person("John", 30); 39 Person alice = new Person("Alice", 25); 40 41 people[john] = "Programmer"; 42 people[alice] = "Designer"; 43 44 foreach (var person in people.Keys) { 45 Console.WriteLine($"{person.Name} is a {people[person]}"); 46 } 47 // Output: 48 // Alice is a Designer 49 // John is a Programmer 50 } 51}
In the code above, we implement the IComparable
interface and override the CompareTo
, Equals
, and GetHashCode
methods. The CompareTo
method ensures that Person
objects are initially sorted by age, and if ages are the same, then by name. Overriding Equals
and GetHashCode
ensures that Person
objects behave consistently when used in collections like SortedDictionary
. The GetHashCode
method returns an integer representation of an object for quick look-ups in hash-based collections.
We've explored how to use custom classes as keys in sorted dictionaries and how comparers work in this context using the IComparable
interface. Implementing CompareTo
is necessary to maintain the order within the SortedDictionary
, while overriding Equals
and GetHashCode
ensures object consistency and identity in collections. Now, prepare for some hands-on exercises to reinforce these concepts.