cyberangles blog

C# | Object.GetHashCode() Method with Examples

In C#, every object inherits from the System.Object class, which provides fundamental methods like ToString(), Equals(), and GetHashCode(). Among these, GetHashCode() plays a critical role in hash-based collections (e.g., Dictionary<TKey, TValue>, HashSet<T>) by enabling efficient lookups, insertions, and deletions. This blog dives deep into GetHashCode(), explaining its purpose, behavior, implementation, best practices, and common pitfalls with practical examples.

2026-06

Table of Contents#

  1. What is Object.GetHashCode()?
  2. How GetHashCode() Works: The Contract
  3. Default Implementation
  4. Overriding GetHashCode()
  5. Best Practices for Overriding
  6. Common Pitfalls
  7. Example Usage
  8. When to Use GetHashCode()
  9. Conclusion
  10. References

What is Object.GetHashCode()?#

The GetHashCode() method is defined in System.Object and returns an int (32-bit signed integer) called a hash code. A hash code is a numeric representation of an object’s state, designed to facilitate fast grouping and retrieval in hash-based data structures.

Key purposes of GetHashCode():

  • Quick Comparison: Hash codes allow collections to group objects into "buckets," reducing the number of equality checks needed to find an object.
  • Efficient Lookups: In hash tables, objects with the same hash code are stored in the same bucket. This makes lookups O(1) on average (vs. O(n) for linear search).

How GetHashCode() Works: The Contract#

The behavior of GetHashCode() is governed by a critical contract defined in the .NET documentation. Violating this contract can lead to unexpected behavior in hash-based collections. The contract states:

  1. Consistency: For the same object, GetHashCode() must return the same value if none of the fields used in the Equals() method have changed. This ensures stability during the object’s lifetime.

  2. Equality Implies Equal Hash Codes: If two objects are equal (i.e., obj1.Equals(obj2) returns true), their hash codes must be equal.

  3. Unequal Hash Codes Imply Inequality: If two objects have different hash codes, they are guaranteed to be unequal. However, the reverse is not true: equal hash codes do not guarantee equality (this is called a "hash collision").

Default Implementation#

The default GetHashCode() implementation varies between reference types and value types.

3.1 Reference Types#

For reference types (e.g., classes), the default GetHashCode() returns a value based on the object’s identity (not its content). This means two distinct instances with identical data will have different hash codes.

Example:

public class Person {
    public string Name { get; set; }
    public int Age { get; set; }
}
 
var alice1 = new Person { Name = "Alice", Age = 30 };
var alice2 = new Person { Name = "Alice", Age = 30 };
 
// Default GetHashCode() uses object identity: different instances → different hash codes
Console.WriteLine(alice1.GetHashCode() == alice2.GetHashCode()); // Output: False

3.2 Value Types#

For value types (e.g., structs, int, string), the default GetHashCode() computes a hash code based on the values of the type’s fields. For example:

  • For primitive types like int, the hash code is the value itself.
  • For custom structs, the default implementation combines the hash codes of all fields (e.g., via XOR), but this can lead to collisions.

Example:

public struct Point {
    public int X { get; set; }
    public int Y { get; set; }
}
 
var point1 = new Point { X = 1, Y = 2 };
var point2 = new Point { X = 1, Y = 2 };
 
// Value type: same field values → same hash code
Console.WriteLine(point1.GetHashCode() == point2.GetHashCode()); // Output: True

Overriding GetHashCode()#

You must override GetHashCode() if you override Equals(). This ensures the contract is maintained: equal objects have equal hash codes.

Why Override?#

  • Semantic Equality: If your type defines "value-based equality" (e.g., two Person objects are equal if their Name and Age match), the default reference-based hash code is incorrect.
  • Hash Collection Compatibility: Hash-based collections (e.g., Dictionary) rely on consistent hash codes for correct behavior.

Best Practices for Overriding#

To implement GetHashCode() correctly, follow these best practices:

1. Use Immutable Fields#

Hash codes should depend only on immutable fields (fields that don’t change after object construction). If a mutable field is used, modifying it will change the hash code, breaking collections that store the object.

2. Combine Hash Codes Carefully#

To minimize collisions, combine hash codes of multiple fields using a formula that spreads values evenly. A common approach is to multiply by a prime number (e.g., 31) and add subsequent hash codes:

public override int GetHashCode() {
    unchecked { // Allow integer overflow (harmless for hash codes)
        int hash = 17; // Initial prime seed
        hash = hash * 31 + Name.GetHashCode(); // 31 is a prime
        hash = hash * 31 + Age;
        return hash;
    }
}
  • Why 31? It’s an odd prime, and 31 * i can be computed as (i << 5) - i, which is efficient for CPUs.

3. Use HashCode.Combine (Modern .NET)#

In .NET Core 2.1+ and .NET 5+, use System.HashCode.Combine() for a concise, optimized way to combine hash codes:

public override int GetHashCode() => HashCode.Combine(Name, Age);

4. Avoid Constant Hash Codes#

Never return a constant (e.g., return 0). This forces all objects into a single bucket, degrading collection performance to O(n).

5. Document Hash Code Stability#

If your type is mutable, document that it should not be used as a key in hash-based collections (since hash codes can change).

Common Pitfalls#

1. Forgetting to Override GetHashCode() When Overriding Equals()#

If Equals() is overridden but GetHashCode() is not, equal objects may have different hash codes, breaking hash collections.

2. Using Mutable Fields#

Modifying a field used in GetHashCode() after adding the object to a HashSet or Dictionary will make it unfindable, as the hash code changes.

3. Poor Hash Code Distribution#

Using simple XOR (hash = a ^ b) for combining fields can cause collisions (e.g., (a=1, b=2) and (a=2, b=1) have the same hash code).

4. Relying on Hash Code Uniqueness#

Hash codes are not unique! Two different objects can have the same hash code (collision). Always use Equals() to confirm equality.

Example Usage#

Example 1: Default Behavior#

public class Car {
    public string Model { get; set; }
    public int Year { get; set; }
}
 
var car1 = new Car { Model = "Tesla", Year = 2023 };
var car2 = new Car { Model = "Tesla", Year = 2023 };
 
// Default GetHashCode() uses reference identity → different hash codes
Console.WriteLine(car1.GetHashCode() == car2.GetHashCode()); // Output: False
Console.WriteLine(car1.Equals(car2)); // Output: False (default Equals checks reference)

Example 2: Overriding Equals() and GetHashCode()#

public class Car {
    public string Model { get; } // Immutable field
    public int Year { get; }     // Immutable field
 
    public Car(string model, int year) {
        Model = model;
        Year = year;
    }
 
    public override bool Equals(object obj) {
        if (obj is not Car other) return false;
        return Model == other.Model && Year == other.Year;
    }
 
    public override int GetHashCode() {
        // Use HashCode.Combine for simplicity and efficiency
        return HashCode.Combine(Model, Year);
    }
}
 
var car1 = new Car("Tesla", 2023);
var car2 = new Car("Tesla", 2023);
 
Console.WriteLine(car1.Equals(car2)); // Output: True
Console.WriteLine(car1.GetHashCode() == car2.GetHashCode()); // Output: True

Example 3: Mutable Objects and Hash Code Instability#

public class MutablePerson {
    public string Name { get; set; } // Mutable field
    public int Age { get; set; }     // Mutable field
 
    public override bool Equals(object obj) {
        if (obj is not MutablePerson other) return false;
        return Name == other.Name && Age == other.Age;
    }
 
    public override int GetHashCode() => HashCode.Combine(Name, Age);
}
 
var hashSet = new HashSet<MutablePerson>();
var person = new MutablePerson { Name = "Bob", Age = 25 };
hashSet.Add(person);
 
// Modify a field used in GetHashCode()
person.Age = 26;
 
// Now, hashSet.Contains(person) may return false!
Console.WriteLine(hashSet.Contains(person)); // Output: False (unexpected behavior)

When to Use GetHashCode()#

GetHashCode() is primarily used implicitly by hash-based collections like:

  • Dictionary<TKey, TValue>: Uses keys’ hash codes to group entries.
  • HashSet<T>: Uses hash codes to avoid duplicate entries.
  • ConcurrentDictionary<TKey, TValue>: Thread-safe hash table.

You rarely call GetHashCode() directly unless implementing custom hash-based logic.

Conclusion#

Object.GetHashCode() is a cornerstone of efficient collection operations in C#. By understanding its contract, default behavior, and best practices for overriding, you can ensure correct and performant hash-based collections. Always override GetHashCode() when overriding Equals(), use immutable fields, and avoid common pitfalls like mutable hash codes.

References#