Saturday, June 13, 2026

C# Why do developers override GetHashCode method

In this post, you will see what does GetHashCode() in a class. The GetHashCode() belongs to object class which super type of all classes. So, GetHashCode() is available to all classes with default implementation. Look at the following example in this regard:

Example1
class Animal
{
    public string Name { get; set; } = string.Empty;
    public override int GetHashCode()
    {
        // using object base class implementation
        return base.GetHashCode();
    }
}
class Program
{
    static void Main()
    {
        Animal animal = new Animal { Name = "Cat" };
        Animal animal2 = new Animal { Name = "Cat" };
        Console.WriteLine(animal.GetHashCode());
        Console.WriteLine(animal2.GetHashCode());
        Animal animal3 = new Animal { Name = "Dog" };
        animal3 = animal2;
        Console.WriteLine(animal3.GetHashCode());
    }
}

The Animal class uses the default implementation. The developer has not provided any custom implementation. When you run the code, you get an integer value for each instance of Animal class. Remember that this value is not identity of animal object. Different objects of a class may have same hash value. But same objects will have same hash code. So, we get same hash value for animal3 and animal2 as they point to same object.

OUTPUT

43942917

59941933

59941933

Note that animal3 and animal2 both points to same animal object, due to animal3=animal2; statement. Therefore, we get same hash code.

Points to Remember

  • If two variables point to same object then their hash code will be same. However, the converse is not necessarily true. 
  • If two objects have same hash code then it does not mean that they are same objects. Hash code may collide for different objects. It also means that hash code are not unique to objects.

Example2

Suppose that we want to generate hash code based on properties of animal object. In this case, developer must provide his own implementation of GetHashCode method. The following example shows custom implementation of GetHashCode method:

class Animal
{
    public string Name { get; set; } = string.Empty;
    public override int GetHashCode()
    {
        // hash code based on current instance property i.e. Name
        return this.Name.GetHashCode();
    }
}
class Program
{
    static void Main()
    {
        Animal animal = new Animal { Name = "Cat" };
        Animal animal2 = new Animal { Name = "Cat" };
        Animal animal3 = new Animal { Name = "Dog" };
        Console.WriteLine(animal.GetHashCode());
        Console.WriteLine(animal2.GetHashCode());
        Console.WriteLine(animal3.GetHashCode());
    }
}

The output is:

238193233

238193233

-374578683

Reason: animal and animal2 produce the same hash code because both contain the same Name.

However, equal hash codes do not prove objects are equal. Different data can still produce identical hash codes.

Remember: Equal hash codes do not prove objects are equal. Different data can still produce identical hash codes.

Application of GetHashCode

When Equals method is overridden in a class for checking value type equality of objects, GetHashCode is also overridden for safety measures. If Equals returns true and GetHashCode returns same value for both objects then it is safe to assume that both objects are same content wise. But remember that GetHashCode is not fullproof.

Example:
class Animal
{
    public string Name { get; set; } = "";
    public override bool Equals(object? obj)
    {
        return obj is Animal a &&
               Name == a.Name;
    }
    public override int GetHashCode()
    {
        return Name.GetHashCode(); // Hash code based on Name of current object
    }
}
Now in the client code, for the same content objects of Animal class, Equals() returns True value and hash values are same for both objects:
class Program
{
    static void Main()
    {
        Animal a1 = new Animal { Name = "Cat" };
        Animal a2 = new Animal { Name = "Cat" };

        Console.WriteLine(a1.Equals(a2));       // True
        Console.WriteLine(a1.GetHashCode());    // same hash code
        Console.WriteLine(a2.GetHashCode());    // same hash code
    }
}

C# Difference Between Reference Type and Runtime Type

In this post, you will see the difference between Reference Type and Runtime Type in C#. One of the most important concepts in C# object-oriented programming is understanding the difference between reference type and runtime type. Many concepts such as upcasting, downcasting, polymorphism, virtual dispatch, is, as, and method overriding become much easier once this distinction is clear.

We will use the following classes(Animal and Bird) to understand the differences:

class Animal
{
    public void Eat()
    {
        Console.WriteLine("Animal eats.");
    }
}
class Bird : Animal
{
    public void Fly()
    {
        Console.WriteLine("Bird can fly.");
    }
}

Reference Type (Compile-Time Type)

  • Reference type is the declared type of a reference variable, parameter, property, field, or expression.
  • It is determined by the compiler and remains fixed for that variable.

Example:
Animal animal = new Bird();

Here: animal is a reference type variable that is of type Animal. The compiler treats animal as an Animal reference.

Reference type determines:

  • Which members are accessible
  • Compile-time type checking
  • Which overload is selected
  • Whether explicit casting is required
Example:
Animal animal = new Bird(); 
animal.Eat(); // Allowed
animal.Fly(); // Compile-time error

Even though the actual object is Bird, the compiler only allows members available in Animal.

Runtime Type (Actual Object Type)

  • Runtime type is the actual type of the object created in memory.
  • It is determined at execution time.

Example:
Animal animal = new Bird();

Here: Runtime Type is Bird. The object itself is a Bird.

Runtime type determines:

  • Which overridden virtual method executes
  • Results of is
  • Whether explicit cast succeeds
  • Actual object behavior
Example:
class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Animal");
    }
}
class Bird : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Bird");
    }
}
class Program
{
    static void Main(string[] args)
    {
        Animal animal = new Bird();
        animal.Speak();
    }
}

Output:

Bird

Although reference type is Animal, runtime type is Bird, so Bird.Speak() executes.

Upcasting and Downcasting

Upcasting is all about converting reference from derived type to base type.

Bird bird = new Bird();
Animal animal = bird;

Result: Reference Type of variable bird changes from Bird to Animal. But Runtime Type remains the same type Bird.

Downcasting is all about converting reference from base type to derived type.

Animal animal = new Bird();
Bird bird = (Bird)animal;

Result: Reference Type changes from Animal to Bird. But Runtime Type remains the same type Bird. Object type never changes.

Using is operator

Animal animal = new Bird();
Console.WriteLine(animal is Bird);

Output:

True

Reason: The is operator checks runtime compatibility. It evaluates the runtime type of left side expression compatibility with the type given in the right operand.

In the given example, equivalent question is: does the object referenced by animal reference variable actually represent a Bird. The answer is Yes.

Common Misconception

Incorrect statement:

Loosely developer may say: "Bird object was upcasted into Animal type." But this is technically wrong.

The correct statement:

Reference to a Bird object was upcasted to Animal. or Bird object is referenced through an Animal variable. The object itself never becomes Animal. 

Remember: In process of upcast and downcast the object itself is never changed to another type (even not to subtype/supertype). The change is only of the type of reference variable that is pointing to the object. The reference type is upcasted/downcasted not the object that is referenced.

"In upcasting and downcasting, the runtime object itself never changes type. Only the compile-time type through which the object is accessed (reference/expression type) changes."

Important: Even without reference variable, a conversion can still occur. For example, the expression: ((Bird)new Bird()).Fly();

Summary

  • Reference Type is determined by compiler. But Runtime Type is determined by CLR at runtime.
  • Reference Type is of Variable / expression type. But Runtime Type is of actual object type.
  • Reference Type decides what members can be accessed. But Runtime Type decides which overridden implementation executes.

Hot Topics