← Back to Articles         
Object Class, Equality & Memory

Object Class, Equality & Memory

Published on 12 Oct 2026     12 min read OOPs
Object Class, Equality & Memory

1. 🔴 What is the object class in C#, and why is it important?

The object class (System.Object) is the ultimate base class of all types in C#.

Every class, struct, enum, and other .NET type derives directly or indirectly from System.Object.

Important methods of the object class

Method

Purpose

ToString()

Returns a string representation

Equals()

Compares objects for equality

GetHashCode()

Returns a hash code

GetType()

Returns runtime type information

ReferenceEquals()

Checks whether references point to the same object

MemberwiseClone()

Creates a shallow copy

Example

class Employee
{
    public string Name { get; set; } = "";
}

Employee emp = new Employee { Name = "John" };

Console.WriteLine(emp.ToString());
Console.WriteLine(emp.GetType().Name);
Console.WriteLine(emp.GetHashCode());

Output (example):

Employee
Employee
54267293

The hash code is not guaranteed to be the same between executions.

Why is object important?

  • Provides common functionality to all .NET types.

  • Supports polymorphism.

  • Enables general-purpose APIs.

  • Provides the foundation for equality and hashing.

  • Supports runtime type inspection.

Interview key point: System.Object is the root of the .NET type system, and all C# types inherit its fundamental behavior.

 

2. 🔴 What is the difference between ==, Equals(), and ReferenceEquals()?

These are used to compare objects, but their behavior differs.

  • == – An operator whose behavior depends on the operand types and operator overloads.

  • Equals() – Compares equality according to the type's implementation.

  • ReferenceEquals() – Checks whether two references point to the same object.

Example

class Employee
{
    public string Name { get; set; } = "";
}

Employee e1 = new Employee { Name = "John" };
Employee e2 = new Employee { Name = "John" };
Employee e3 = e1;

Console.WriteLine(e1 == e2);
Console.WriteLine(e1.Equals(e2));
Console.WriteLine(ReferenceEquals(e1, e2));
Console.WriteLine(ReferenceEquals(e1, e3));

Output:

False
False
False
True

Explanation:

  • e1 and e2 are different objects.

  • e3 references the same object as e1.

  • The Employee class does not override equality behavior.

Comparison

==

Equals()

ReferenceEquals()

Equality operator

Virtual equality method

Static method

Can be overloaded

Can be overridden

Cannot be overridden

May compare values or references

May compare values or references

Compares reference identity

Depends on operand types

Depends on implementation

Always checks object identity

Important exception: String

string s1 = new string("Hello".ToCharArray());
string s2 = new string("Hello".ToCharArray());

Console.WriteLine(s1 == s2);                 // True
Console.WriteLine(s1.Equals(s2));            // True
Console.WriteLine(ReferenceEquals(s1, s2));  // False

Key point: For ordinary classes without equality customization, == and Equals() normally compare references. For types such as string, they compare values.

 

3. 🔴 What is boxing and unboxing?

Boxing converts a value type into an object or an implemented interface reference, generally allocating an object on the managed heap.

Unboxing extracts the value type from a boxed object through an explicit conversion.

Example

int number = 100;

// Boxing
object obj = number;

// Unboxing
int result = (int)obj;

Console.WriteLine(result); // 100

Execution flow

int number = 100
       |
       v
Boxing
       |
       v
object obj = number
       |
       v
Boxed copy stored in managed heap
       |
       v
Unboxing
       |
       v
int result = (int)obj

Important points

  • Boxing generally involves heap allocation and copying the value.

  • Unboxing requires a compatible value type.

  • Incorrect unboxing can throw InvalidCastException.

  • Excessive boxing/unboxing can affect performance.

Example: Incorrect unboxing

object obj = 100;

// InvalidCastException
double value = (double)obj;

Correct approach:

int value = (int)obj;
double result = value;

How to avoid boxing?

Use generics:

List<int> numbers = new List<int>();

numbers.Add(10);
numbers.Add(20);

Unlike a nongeneric ArrayList, List<int> does not require boxing integers when adding them.

Interview key point: Boxing converts a value type to an object representation; unboxing retrieves the value. Generics help avoid unnecessary boxing.

 

4. 🔴 What is the difference between shallow copy and deep copy?

Shallow copy creates a new object but copies references to nested objects.

Deep copy creates a new object and independent copies of the nested mutable objects that need to be duplicated.

Example: Shallow copy

class Address
{
    public string City { get; set; } = "";
}

class Employee
{
    public string Name { get; set; } = "";
    public Address Address { get; set; } = new();

    public Employee ShallowCopy()
    {
        return (Employee)MemberwiseClone();
    }
}

Usage:

Employee e1 = new Employee
{
    Name = "John",
    Address = new Address { City = "Chennai" }
};

Employee e2 = e1.ShallowCopy();

e2.Name = "David";
e2.Address.City = "Coimbatore";

Console.WriteLine(e1.Name);         // John
Console.WriteLine(e1.Address.City); // Coimbatore

Explanation:

  • Name is a string, and assigning a new string to e2.Name does not change e1.Name.

  • Address is a reference type, so both objects share the same Address instance.

Example: Deep copy

Add this method to Employee:

public Employee DeepCopy()
{
    return new Employee
    {
        Name = this.Name,
        Address = new Address
        {
            City = this.Address.City
        }
    };
}

Usage:

Employee e2 = e1.DeepCopy();

e2.Address.City = "Madurai";

Console.WriteLine(e1.Address.City); // Chennai
Console.WriteLine(e2.Address.City); // Madurai

Assuming e1.Address.City was initially Chennai.

Comparison

Shallow copy

Deep copy

Creates a new outer object

Creates independent object graph as needed

Nested references are shared

Nested mutable objects are copied

Usually faster

Usually more expensive

Less memory allocation

More memory allocation

Changes to shared nested objects are visible in both

Changes to copied nested objects remain independent

Key point: Shallow copy duplicates the outer object, while deep copy duplicates nested mutable state to achieve independence.

 

5. 🟡 What is the purpose of GetHashCode()?

GetHashCode() returns an integer hash code used by hash-based collections to organize and locate objects efficiently.

It is commonly used by:

  • Dictionary<TKey, TValue>

  • HashSet<T>

  • Hashtable

Example

string name = "John";

int hash = name.GetHashCode();

Console.WriteLine(hash);

Real-world example

Dictionary<string, int> employees = new();

employees.Add("John", 1001);
employees.Add("David", 1002);

Console.WriteLine(employees["John"]);

Output:

1001

The dictionary uses hashing and equality comparisons to find the matching key.

Important rules

  1. Equal objects must produce equal hash codes.

  2. Different objects can produce the same hash code.

  3. Hash codes are not guaranteed to remain the same across application executions.

  4. Hash codes should remain stable while an object is used as a hash-based collection key.

  5. Hash codes are not suitable for encryption or secure identification.

Key point: GetHashCode() improves lookup efficiency in hash-based collections, but it does not uniquely identify an object.

 

6. 🟡 Why should Equals() and GetHashCode() be overridden together?

When two objects are considered equal by Equals(), they must return the same hash code.

Otherwise, hash-based collections may behave incorrectly.

Example

class Employee
{
    public int Id { get; set; }
    public string Name { get; set; } = "";

    public override bool Equals(object? obj)
    {
        return obj is Employee emp && Id == emp.Id;
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

Usage:

Employee e1 = new Employee { Id = 1, Name = "John" };
Employee e2 = new Employee { Id = 1, Name = "David" };

Console.WriteLine(e1.Equals(e2)); // True

Console.WriteLine(
    e1.GetHashCode() == e2.GetHashCode()
); // True

Here, equality is based on Id, not Name.

Why is it important?

Consider:

HashSet<Employee> employees = new();

employees.Add(e1);
employees.Add(e2);

Console.WriteLine(employees.Count); // 1

Both employees are treated as equal because they have the same Id.

Important rules

  • If Equals() returns true, hash codes must match.

  • Equal hash codes do not guarantee equality.

  • When customizing equality for a class, also consider implementing IEquatable<T> and defining consistent == and != operators.

  • Avoid changing fields used in hashing while an object is stored in a HashSet or used as a dictionary key.

Key point: Override Equals() and GetHashCode() together to maintain consistent equality behavior in collections.

 

7. 🟡 What is the difference between MemberwiseClone() and a custom deep copy?

MemberwiseClone() is a protected method inherited from System.Object that creates a shallow copy.

A custom deep-copy method explicitly creates independent copies of nested objects.

Example

class Employee
{
    public string Name { get; set; } = "";

    public Employee Clone()
    {
        return (Employee)MemberwiseClone();
    }
}

Usage:

Employee e1 = new Employee { Name = "John" };
Employee e2 = e1.Clone();

Console.WriteLine(ReferenceEquals(e1, e2)); // False

Comparison

MemberwiseClone()

Custom deep copy

Built-in method

Developer-defined implementation

Creates shallow copy

Creates deep copy

Copies field values

Copies selected nested objects

Shares nested object references

Can create independent nested objects

Less code

Requires explicit copying logic

Protected method

Access depends on implementation

Important: MemberwiseClone() copies fields, including backing fields of auto-properties. It does not recursively clone referenced objects.

Key point: Use MemberwiseClone() for shallow copying; implement a custom deep copy when nested mutable objects must be independent.

 

8. 🟡 What is the difference between stack and heap memory in C#?

C# applications use stack and heap memory for different purposes.

  • Stack – Primarily stores method call frames, local variables, and execution-related information.

  • Heap – Stores dynamically allocated objects managed by the Garbage Collector.

Example

class Employee
{
    public int Id { get; set; }
}

void Test()
{
    int number = 100;

    Employee emp = new Employee();
    emp.Id = 10;
}

Conceptual memory representation

STACK                         MANAGED HEAP
------------------            ------------------
Test() frame

number = 100

emp (reference)  ------------> Employee Object
                               Id = 10
------------------            ------------------

This is a conceptual example. The JIT compiler can optimize storage, so the exact physical layout is not guaranteed.

Comparison

Stack

Heap

Stores execution frames and locals

Stores dynamically allocated objects

Very efficient allocation

Managed allocation with GC

Frame lifetime tied to method execution

Object lifetime depends on reachability

Limited stack space

Larger managed memory area

Automatically unwinds on return

GC reclaims unreachable objects

Important interview clarification

Value types are not always stored on the stack.

For example:

class Employee
{
    public int Age { get; set; }
}

Age is a value type, but it is part of the Employee object, which is normally allocated on the managed heap.

Similarly, a struct can be stored inside a heap-allocated object or array.

Key point: Value types and reference types describe value semantics and reference semantics, not an absolute rule about stack versus heap allocation.

 

9. 🟢 How does the Garbage Collector manage object memory?

The Garbage Collector (GC) automatically manages managed heap memory in .NET.

It identifies objects that are no longer reachable from application roots and reclaims their memory.

Example

class Employee
{
    public string Name { get; set; } = "";
}

void CreateEmployee()
{
    Employee emp = new Employee
    {
        Name = "John"
    };
}

Once CreateEmployee() completes, the object can become eligible for garbage collection if no reachable references remain.

Garbage Collection flow

Object Created
      |
      v
Allocated on Managed Heap
      |
      v
Application Uses Object
      |
      v
Object Becomes Unreachable
      |
      v
Garbage Collector Runs
      |
      v
Identifies Reachable Objects
      |
      v
Reclaims Unreachable Memory
      |
      v
May Compact Heap

GC generations

.NET uses generational garbage collection.

Generation

Purpose

Generation 0

Newly allocated, short-lived objects

Generation 1

Intermediate objects that survived collection

Generation 2

Longer-lived objects

Large Object Heap (LOH)

Large allocations, generally 85 KB or more

Important points

  • GC automatically manages managed memory.

  • Objects are collected based on reachability, not simply because a method has ended.

  • GC uses generations to improve performance.

  • GC does not guarantee immediate cleanup.

  • GC.Collect() can request collection, but should normally be avoided in application code.

  • Dispose() is still required for timely release of disposable resources.

Key point: Garbage Collection manages memory automatically, while IDisposable supports deterministic resource cleanup.

 

10. 🟢 What are weak references in C#?

A weak reference allows an application to reference an object without preventing the Garbage Collector from collecting it.

Normally, a strongly reachable object remains alive. A weak reference does not keep its target alive by itself.

Example

class Employee
{
    public string Name { get; set; } = "";
}

Employee emp = new Employee { Name = "John" };

WeakReference<Employee> weakRef = new(emp);

// Remove strong reference
emp = null!;

if (weakRef.TryGetTarget(out Employee? target))
{
    Console.WriteLine(target.Name);
}
else
{
    Console.WriteLine("Object collected");
}

Explanation:

  • WeakReference<T> holds a weak reference.

  • TryGetTarget() checks whether the object is still available.

  • If GC has collected the object, TryGetTarget() returns false.

  • The object may still be available if garbage collection has not occurred.

Strong reference vs weak reference

Strong reference

Weak reference

Keeps object reachable

Does not prevent collection

Normal object reference

Uses WeakReference<T>

Object stays alive while strongly reachable

Object may be collected

Used in normal application logic

Used in specialized memory-sensitive scenarios

Real-world use cases

  • Caching objects that can be recreated.

  • Holding optional references to large objects.

  • Memory-sensitive applications.

  • Specialized object-tracking scenarios.

For most ASP.NET Core caching requirements, IMemoryCache or distributed caching is preferable to implementing caching with weak references.

Key point: Weak references allow access to an object without extending its lifetime.

 

Quick Interview Revision

Concept

Key point

System.Object

Base class of all C# types

==

Type-dependent equality operator

Equals()

Compares equality based on implementation

ReferenceEquals()

Checks object identity

Boxing

Value type → Object representation

Unboxing

Boxed object → Value type

Shallow copy

Nested references shared

Deep copy

Independent nested mutable objects

GetHashCode()

Supports hash-based collections

Equals() + GetHashCode()

Must maintain equality consistency

MemberwiseClone()

Creates shallow copy

Stack

Execution frames and local storage

Heap

Dynamically allocated objects

Garbage Collector

Reclaims unreachable managed objects

Gen 0, 1, 2

Generational garbage collection

Weak reference

Does not prevent object collection

Interview Priority – Senior .NET Developer / Technical Lead

Question

Priority

object class and its methods

🔴 Must know

== vs Equals() vs ReferenceEquals()

🔴 Must know

Boxing and unboxing

🔴 Must know

Shallow copy vs deep copy

🔴 Must know

Equals() and GetHashCode()

🔴 Must know

Stack vs heap memory

🔴 Must know

Garbage Collection and generations

🔴 Must know

MemberwiseClone()

🟡 Should know

Weak references

🟢 Nice to know

Technical Lead interview focus: Pay particular attention to equality and hashing in Dictionary/HashSet, avoiding boxing in performance-critical code, managed memory behavior, and the difference between Garbage Collection and deterministic resource cleanup using IDisposable.

← Back to Articles