# C# `==` vs `Equals()`: Khác nhau thế nào, khi nào dùng và những lỗi thường gặp

Trong C#, cả `==` và `Equals()` đều được dùng để so sánh hai giá trị hoặc hai object.

Ví dụ:

“`csharp
a == b
“`

và:

“`csharp
a.Equals(b)
“`

Nhìn qua có vẻ giống nhau, nhưng bản chất của chúng khác nhau:

– `==` là **operator**.
– `Equals()` là **method**.
– Với value type, cả hai thường so sánh giá trị.
– Với reference type, behavior phụ thuộc vào type đó có override hoặc overload equality hay không.
– `string` là trường hợp đặc biệt vì dù là reference type nhưng `==` được overload để so sánh nội dung.
– Nếu custom `Equals()`, cần quan tâm đến `GetHashCode()`.
– Nếu custom `==`, nên giữ behavior nhất quán với `Equals()`.

Đây là một chủ đề rất hay xuất hiện trong phỏng vấn .NET.

—

# 1. `==` là gì?

`==` là equality operator.

Ví dụ:

“`csharp
int a = 10;
int b = 10;

Console.WriteLine(a == b);
“`

Kết quả:

“`text
true
“`

Với các primitive value type như:

“`text
int
long
double
bool
char
“`

operator `==` thường so sánh giá trị.

Ví dụ:

“`csharp
int x = 10;
int y = 10;

x == y;
“`

thực chất đang hỏi:

“`text
10 có bằng 10 không?
“`

—

# 2. `Equals()` là gì?

`Equals()` là method được định nghĩa từ `System.Object`.

Mọi type trong C# đều kế thừa từ `object`, do đó đều có method:

“`csharp
Equals()
“`

Ví dụ:

“`csharp
int a = 10;
int b = 10;

Console.WriteLine(a.Equals(b));
“`

Kết quả:

“`text
true
“`

Với value type như `int`, `Equals()` so sánh value.

—

# 3. Reference Type làm mọi thứ phức tạp hơn

Giả sử có class:

“`csharp
public class User
{
public string Name { get; set; }
}
“`

Tạo hai object:

“`csharp
var user1 = new User
{
Name = “Vu”
};

var user2 = new User
{
Name = “Vu”
};
“`

Hai object có cùng dữ liệu.

Nhưng:

“`csharp
Console.WriteLine(user1 == user2);
“`

kết quả:

“`text
false
“`

Tại sao?

Vì mặc định đối với custom reference type chưa overload `==`, operator này so sánh reference.

Có thể hình dung:

“`text
user1
↓
Object A
Name = “Vu”
“`

và:

“`text
user2
↓
Object B
Name = “Vu”
“`

Data giống nhau nhưng đây là hai object khác nhau trong memory.

—

# 4. `Equals()` mặc định với reference type

Nếu tiếp tục:

“`csharp
Console.WriteLine(user1.Equals(user2));
“`

kết quả cũng thường là:

“`text
false
“`

nếu class `User` chưa override `Equals()`.

Implementation mặc định từ `object` đối với reference type về cơ bản kiểm tra reference identity.

Có thể hiểu:

“`text
user1 == user2
↓
different objects
↓
false
“`

và:

“`text
user1.Equals(user2)
↓
default object equality
↓
different objects
↓
false
“`

—

# 5. Override `Equals()` để định nghĩa Logical Equality

Giả sử business requirement của chúng ta là:

> Hai user được coi là giống nhau nếu có cùng Email.

Có thể override:

“`csharp
public class User
{
public string Email { get; set; }

public override bool Equals(object? obj)
{
if (obj is not User other)
return false;

return Email == other.Email;
}

public override int GetHashCode()
{
return Email.GetHashCode();
}
}
“`

Bây giờ:

“`csharp
var user1 = new User
{
Email = “vu@example.com”
};

var user2 = new User
{
Email = “vu@example.com”
};
“`

gọi:

“`csharp
user1.Equals(user2);
“`

sẽ trả:

“`text
true
“`

Dù:

“`text
user1 != user2 về mặt reference
“`

nhưng theo business logic:

“`text
Email giống nhau
“`

nên chúng được xem là logically equal.

—

# 6. Reference Equality và Value Equality

Đây là hai khái niệm rất quan trọng.

## Reference Equality

Hỏi:

> Hai biến có đang trỏ tới cùng một object không?

Ví dụ:

“`csharp
var user1 = new User();

var user2 = user1;
“`

Có thể hình dung:

“`text
user1 ──┐
├──> User Object
user2 ──┘
“`

Hai variable cùng reference.

—

## Value / Logical Equality

Hỏi:

> Hai object có được xem là giống nhau dựa trên dữ liệu/business meaning không?

Ví dụ:

“`text
User A:
Email = vu@example.com

User B:
Email = vu@example.com
“`

Dù là hai object khác nhau, chúng có thể được business coi là equal.

Đây là thứ chúng ta thường implement qua:

“`csharp
Equals()
“`

—

# 7. `ReferenceEquals()` là gì?

Nếu thực sự muốn kiểm tra hai biến có trỏ tới cùng một object không, có thể dùng:

“`csharp
object.ReferenceEquals(a, b);
“`

Ví dụ:

“`csharp
var user1 = new User();

var user2 = user1;

Console.WriteLine(
object.ReferenceEquals(user1, user2)
);
“`

Kết quả:

“`text
true
“`

Nhưng:

“`csharp
var user1 = new User();
var user2 = new User();
“`

thì:

“`csharp
object.ReferenceEquals(user1, user2);
“`

trả:

“`text
false
“`

—

# 8. `string` là trường hợp đặc biệt

`string` là reference type.

Nhưng thử:

“`csharp
string a = new string(
“hello”.ToCharArray()
);

string b = new string(
“hello”.ToCharArray()
);
“`

Đây là hai object string riêng biệt.

Nhưng:

“`csharp
Console.WriteLine(a == b);
“`

kết quả:

“`text
true
“`

Và:

“`csharp
Console.WriteLine(a.Equals(b));
“`

cũng:

“`text
true
“`

Tại sao?

Vì `string` đã overload operator:

“`csharp
==
“`

để so sánh nội dung.

—

# 9. Vì vậy không nên nói “`==` luôn so reference”

Đây là một câu trả lời phỏng vấn không chính xác.

Không nên nói:

> `==` checks reference and Equals checks value.

Điều này chỉ đúng trong một số trường hợp.

Chính xác hơn là:

> Behavior của `==` phụ thuộc vào implementation của type và operator overload.

Ví dụ:

“`text
int
“`

`==` so value.

“`text
string
“`

`==` so content.

“`text
custom class
“`

mặc định thường so reference.

“`text
record
“`

mặc định có value-based equality.

—

# 10. `==` có thể Overload

Custom class có thể định nghĩa lại operator:

“`csharp
public class User
{
public string Email { get; set; }

public static bool operator ==(
User? left,
User? right)
{
if (ReferenceEquals(left, right))
return true;

if (left is null || right is null)
return false;

return left.Email == right.Email;
}

public static bool operator !=(
User? left,
User? right)
{
return !(left == right);
}
}
“`

Bây giờ:

“`csharp
var user1 = new User
{
Email = “vu@example.com”
};

var user2 = new User
{
Email = “vu@example.com”
};

Console.WriteLine(user1 == user2);
“`

có thể trả:

“`text
true
“`

—

# 11. Nếu overload `==` thì nên overload `!=`

Một rule quan trọng trong C#:

Nếu custom:

“`csharp
==
“`

thì cũng nên định nghĩa:

“`csharp
!=
“`

Ví dụ:

“`csharp
public static bool operator !=(
User? left,
User? right)
{
return !(left == right);
}
“`

Nếu hai operator có behavior không nhất quán, code rất khó hiểu.

—

# 12. `Equals()` có thể Override

Khác với `==`, `Equals()` là virtual method.

Có thể override:

“`csharp
public override bool Equals(object? obj)
{
…
}
“`

Điều này cho phép class định nghĩa:

“`text
Logical Equality
“`

riêng.

—

# 13. Override Equals thì phải quan tâm GetHashCode

Đây là rule cực kỳ quan trọng:

> Nếu override `Equals()`, phải override `GetHashCode()`.

Ví dụ:

“`csharp
public class User
{
public string Email { get; init; }

public override bool Equals(object? obj)
{
return obj is User other
&& Email == other.Email;
}

public override int GetHashCode()
{
return Email.GetHashCode();
}
}
“`

Tại sao?

Vì các hash-based collection như:

“`text
Dictionary
HashSet
“`

sử dụng:

“`text
GetHashCode()
+
Equals()
“`

để xác định equality.

—

# 14. HashSet hoạt động như thế nào?

Giả sử:

“`csharp
var users = new HashSet();
“`

Khi add:

“`csharp
users.Add(user);
“`

HashSet có thể làm conceptually:

“`text
GetHashCode()
↓
Find hash bucket
↓
Equals()
↓
Same object logically?
“`

Nếu:

“`text
Equals() = true
“`

thì hai object logically phải tạo hash code phù hợp với equality contract.

—

# 15. Equality Contract

Một rule quan trọng:

Nếu:

“`text
a.Equals(b) == true
“`

thì:

“`text
a.GetHashCode() == b.GetHashCode()
“`

phải đúng.

Nhưng chiều ngược lại không bắt buộc.

Hai object có thể có cùng hash code nhưng vẫn:

“`text
Equals() == false
“`

Đó gọi là:

“`text
Hash Collision
“`

—

# 16. Bug khi override Equals nhưng không override GetHashCode

Giả sử:

“`csharp
public override bool Equals(object? obj)
{
return obj is User other
&& Email == other.Email;
}
“`

nhưng không override:

“`csharp
GetHashCode()
“`

Hai object:

“`text
User A
Email = vu@example.com

User B
Email = vu@example.com
“`

có thể:

“`text
Equals = true
“`

nhưng:

“`text
HashCode khác nhau
“`

Khi cho vào:

“`csharp
HashSet
“`

có thể xảy ra behavior không đúng kỳ vọng.

—

# 17. Null Handling khác nhau

`==` thường rất tiện khi check null.

Ví dụ:

“`csharp
User? user = null;

if (user == null)
{
Console.WriteLine(“User is null”);
}
“`

Không có vấn đề gì.

Nhưng:

“`csharp
user.Equals(null);
“`

sẽ gây:

“`text
NullReferenceException
“`

vì:

“`text
user
“`

đang là null và bạn cố gọi instance method.

—

# 18. `object.Equals()` xử lý null an toàn hơn

Có static method:

“`csharp
object.Equals(a, b);
“`

Ví dụ:

“`csharp
object.Equals(null, null);
“`

trả:

“`text
true
“`

Và:

“`csharp
object.Equals(user1, user2);
“`

sẽ gọi equality logic phù hợp.

Điều này hữu ích khi object có thể null.

—

# 19. `ReferenceEquals()` khác `Equals()`

Ví dụ:

“`csharp
var user1 = new User
{
Email = “vu@example.com”
};

var user2 = new User
{
Email = “vu@example.com”
};
“`

Nếu `Equals()` đã override:

“`csharp
user1.Equals(user2);
“`

có thể:

“`text
true
“`

Nhưng:

“`csharp
ReferenceEquals(user1, user2);
“`

vẫn:

“`text
false
“`

Vì:

“`text
Logical equality
!=
Reference identity
“`

—

# 20. Record trong C#

C# `record` được thiết kế với value-based equality.

Ví dụ:

“`csharp
public record User(
string Name,
string Email
);
“`

Tạo:

“`csharp
var user1 = new User(
“Vu”,
“vu@example.com”
);

var user2 = new User(
“Vu”,
“vu@example.com”
);
“`

Sau đó:

“`csharp
user1 == user2;
“`

trả:

“`text
true
“`

Và:

“`csharp
user1.Equals(user2);
“`

cũng:

“`text
true
“`

Mặc dù chúng là hai object riêng biệt.

—

# 21. Class vs Record

Ví dụ class:

“`csharp
public class User
{
public string Name { get; init; }
}
“`

“`csharp
var a = new User { Name = “Vu” };
var b = new User { Name = “Vu” };

a == b;
“`

mặc định:

“`text
false
“`

Trong khi:

“`csharp
public record User(string Name);
“`

“`csharp
var a = new User(“Vu”);
var b = new User(“Vu”);

a == b;
“`

kết quả:

“`text
true
“`

Vì record mặc định sử dụng value-based equality.

—

# 22. Record phù hợp với Value Object

Ví dụ trong Domain-Driven Design:

“`csharp
public record Money(
decimal Amount,
string Currency
);
“`

Hai object:

“`csharp
new Money(100, “USD”)
“`

và:

“`csharp
new Money(100, “USD”)
“`

về business có thể được xem là cùng giá trị.

Do đó record rất phù hợp với:

“`text
DTO
Value Object
Immutable Data
Message
Command
Event
“`

—

# 23. `IEquatable<>` là gì?

Nếu muốn implement equality chuyên nghiệp hơn, đặc biệt với custom type, có thể implement:

“`csharp
IEquatable
“`

Ví dụ:

“`csharp
public class User : IEquatable
{
public string Email { get; init; } = string.Empty;

public bool Equals(User? other)
{
if (other is null)
return false;

return Email == other.Email;
}

public override bool Equals(object? obj)
{
return Equals(obj as User);
}

public override int GetHashCode()
{
return Email.GetHashCode();
}
}
“`

### Lợi ích của `IEquatable<>`

`IEquatable<>` giúp:

– Làm equality contract rõ ràng hơn.
– Tránh một số boxing hoặc type conversion không cần thiết.
– Hữu ích với generic collections.
– Cho phép custom type định nghĩa rõ thế nào là “equal”.

—

## 24. Khi nào nên implement `IEquatable<>`?

`IEquatable<>` phù hợp khi custom type:

– Thường xuyên được so sánh.
– Được sử dụng trong collections.
– Là Value Object.
– Có business equality rõ ràng.
– Được dùng trong Dictionary.
– Được dùng trong HashSet.

Ví dụ một số type phù hợp:

“`text
Money
EmailAddress
Coordinate
ProductCode
Version
DateRange
“`

—

## 25. Equality với `struct`

`struct` là value type.

Ví dụ:

“`csharp
public struct Point
{
public int X { get; set; }

public int Y { get; set; }
}
“`

Value type có equality behavior khác reference type.

Tuy nhiên, với custom struct, nếu cần performance tốt hoặc equality semantic rõ ràng thì vẫn nên cân nhắc implement:

“`csharp
IEquatable
“`

Ví dụ:

“`csharp
public readonly struct Point : IEquatable
{
public int X { get; }

public int Y { get; }

public Point(int x, int y)
{
X = x;
Y = y;
}

public bool Equals(Point other)
{
return X == other.X && Y == other.Y;
}

public override bool Equals(object? obj)
{
return obj is Point other && Equals(other);
}

public override int GetHashCode()
{
return HashCode.Combine(X, Y);
}
}
“`

Điều này đặc biệt hữu ích nếu struct được sử dụng nhiều trong:

“`text
Dictionary
HashSet
LINQ
Collections
“`

—

## 26. Một lỗi thiết kế phổ biến: Equality dựa trên mutable property

Ví dụ:

“`csharp
public class User
{
public string Email { get; set; } = string.Empty;

public override int GetHashCode()
{
return Email.GetHashCode();
}
}
“`

Sau đó thêm object vào `HashSet`:

“`csharp
var user = new User
{
Email = “old@example.com”
};

var users = new HashSet
{
user
};
“`

Tiếp theo thay đổi:

“`csharp
user.Email = “new@example.com”;
“`

Lúc này hash code của object đã thay đổi.

Nhưng object ban đầu đã được lưu trong bucket dựa trên hash code cũ.

Khi đó:

“`csharp
users.Contains(user);
“`

có thể hoạt động ngoài kỳ vọng.

—

## 27. Best Practice với Hash Equality

Nếu một property tham gia vào:

“`text
Equals()
+
GetHashCode()
“`

thì property đó tốt nhất nên:

“`text
Immutable
“`

hoặc ít nhất:

> Không nên thay đổi trong thời gian object đang nằm trong hash-based collection.

Ví dụ:

“`csharp
public class User
{
public string Email { get; init; } = string.Empty;
}
“`

sẽ an toàn hơn:

“`csharp
public string Email { get; set; }
“`

nếu `Email` được dùng để tính equality.

—

# 28. Entity Equality trong Domain Model

Trong Domain-Driven Design, một Entity thường có:

“`text
Identity
“`

Ví dụ:

“`csharp
public class User
{
public Guid Id { get; init; }

public string Name { get; set; } = string.Empty;
}
“`

Giả sử có hai user:

“`text
User A
Id = 111
Name = Vu
“`

và:

“`text
User B
Id = 222
Name = Vu
“`

Dù `Name` giống nhau, đây vẫn là hai user khác nhau.

Với Entity, equality thường dựa trên:

“`text
Identity
“`

chứ không phải tất cả property.

Ví dụ:

“`csharp
public bool Equals(User? other)
{
if (other is null)
return false;

return Id == other.Id;
}
“`

—

# 29. Value Object Equality

Value Object thì ngược lại.

Value Object thường không có identity độc lập.

Ví dụ:

“`text
Money
Amount = 100
Currency = USD
“`

Nếu hai object `Money` có:

“`text
Amount giống nhau
Currency giống nhau
“`

thì chúng thường được xem là equal.

Ví dụ:

“`csharp
public record Money(decimal Amount, string Currency);
“`

Sau đó:

“`csharp
var a = new Money(100, “USD”);
var b = new Money(100, “USD”);

Console.WriteLine(a == b);
“`

Kết quả:

“`text
true
“`

Đây là lý do `record` rất phù hợp cho Value Object.

—

# 30. `==` và Polymorphism

Một nuance quan trọng:

“`csharp
==
“`

là operator overload.

Trong khi:

“`csharp
Equals()
“`

là method có thể tham gia virtual dispatch.

Ví dụ:

“`csharp
public class Animal
{
public override bool Equals(object? obj)
{
…
}
}
“`

`Equals()` có thể được override.

Trong khi `==` được resolve dựa trên operator overload và compile-time type theo quy tắc overload resolution.

Vì vậy:

> `==` và `Equals()` không nên được xem là hoàn toàn giống nhau về cơ chế dispatch.

Đây là một lý do khiến custom equality phải được thiết kế cẩn thận.

—

# 31. Không nên custom equality nếu không có business reason

Không phải class nào cũng cần override:

“`csharp
Equals()
“`

hoặc:

“`csharp
GetHashCode()
“`

Ví dụ:

“`csharp
public class UserService
{
}
“`

Không có lý do gì để `UserService` cần custom equality.

Equality nên có semantic rõ ràng.

Một số ví dụ hợp lý:

“`text
Money
Coordinate
EmailAddress
ProductCode
Version
DateRange
“`

—

# 32. So sánh tổng quát

| Đặc điểm | `==` | `Equals()` | `ReferenceEquals()` |
|—|—|—|—|
| Loại | Operator | Method | Static Method |
| Có thể customize | Overload | Override | Không |
| Value Type | Thường value comparison | Value comparison | Không phù hợp để kiểm tra value equality |
| Custom Reference Type mặc định | Reference equality | Reference equality | Reference equality |
| `string` | Content comparison | Content comparison | Reference comparison |
| `record` | Value-based | Value-based | Reference comparison |
| Null handling | Thường tiện | Instance method cần chú ý `null` | Safe |
| Polymorphic | Operator overload resolution | Có virtual dispatch | Không |

—

# 33. Câu hỏi phỏng vấn: `==` và `Equals()` khác gì nhau?

Một câu trả lời tốt:

> `==` is an operator while `Equals()` is a virtual method defined on `System.Object`. The exact behavior depends on the type. For custom reference types, `==` normally compares references unless overloaded, while `Equals()` can be overridden to define logical equality. Types such as `string` and records provide value-based equality behavior.

Có thể trả lời ngắn hơn bằng tiếng Việt:

> `==` là operator, còn `Equals()` là method. Behavior của `==` phụ thuộc vào type và operator overload, còn `Equals()` có thể được override để định nghĩa logical equality.

—

# 34. Câu hỏi phỏng vấn: `==` có luôn so reference không?

Không.

Behavior của `==` phụ thuộc vào type.

Ví dụ:

“`csharp
string a = “hello”;
string b = “hello”;

Console.WriteLine(a == b);
“`

Kết quả:

“`text
true
“`

Mặc dù `string` là reference type.

Lý do:

> `System.String` overload equality operator để so sánh nội dung thay vì object reference.

Vì vậy không nên nhớ:

“`text
== luôn luôn so reference
“`

Đây là sai.

—

# 35. Câu hỏi phỏng vấn: Vì sao override `Equals()` phải override `GetHashCode()`?

Các collection như:

“`text
Dictionary
HashSet
“`

sử dụng:

“`text
GetHashCode()
“`

để xác định bucket.

Sau đó sử dụng:

“`text
Equals()
“`

để kiểm tra chính xác object.

Equality contract yêu cầu:

“`text
Nếu A.Equals(B) == true
“`

thì:

“`text
A.GetHashCode() == B.GetHashCode()
“`

phải đúng.

Ví dụ:

“`csharp
public override bool Equals(object? obj)
{
return obj is User other &&
Email == other.Email;
}

public override int GetHashCode()
{
return Email.GetHashCode();
}
“`

Nếu override `Equals()` nhưng không đảm bảo `GetHashCode()` nhất quán, hash-based collections có thể hoạt động sai.

—

# 36. Câu hỏi phỏng vấn: `ReferenceEquals()` dùng để làm gì?

`ReferenceEquals()` kiểm tra:

> Hai reference có đang trỏ tới đúng cùng một object instance hay không.

Ví dụ:

“`csharp
var a = new User();
var b = a;

Console.WriteLine(
ReferenceEquals(a, b)
);
“`

Kết quả:

“`text
true
“`

Nhưng:

“`csharp
var a = new User();
var b = new User();

Console.WriteLine(
ReferenceEquals(a, b)
);
“`

Kết quả:

“`text
false
“`

Ngay cả khi hai object có cùng dữ liệu.

—

# 37. Câu hỏi phỏng vấn: `string` là reference type, tại sao `==` lại trả về `true`?

Ví dụ:

“`csharp
string a = “hello”;
string b = “hello”;

Console.WriteLine(a == b);
“`

Kết quả:

“`text
true
“`

Câu trả lời:

> Because `System.String` overloads the equality operator to compare character values rather than object references.

Hay nói đơn giản:

> `string` đã overload `==` để so sánh nội dung.

—

# 38. Record khác Class ở Equality thế nào?

Một normal class:

“`csharp
public class User
{
public string Name { get; init; } = string.Empty;
}
“`

Ví dụ:

“`csharp
var a = new User
{
Name = “Vu”
};

var b = new User
{
Name = “Vu”
};

Console.WriteLine(a == b);
“`

Kết quả mặc định:

“`text
false
“`

Vì đây là hai object khác nhau.

Trong khi record:

“`csharp
public record User(string Name);
“`

Ví dụ:

“`csharp
var a = new User(“Vu”);
var b = new User(“Vu”);

Console.WriteLine(a == b);
“`

Kết quả:

“`text
true
“`

Có thể nhớ:

> Normal class mặc định dùng reference equality, còn record cung cấp value-based equality mặc định.

—

# 39. Cách nhớ nhanh

## `==`

“`text
==
↓
Operator
↓
Behavior depends on type
↓
Can be overloaded
“`

## `Equals()`

“`text
Equals()
↓
Method
↓
Logical equality
↓
Can be overridden
“`

## `ReferenceEquals()`

“`text
ReferenceEquals()
↓
Same exact object?
“`

—

# 40. Ví dụ tổng hợp

“`csharp
public record User(
string Name,
string Email
);

var a = new User(
“Vu”,
“vu@example.com”
);

var b = new User(
“Vu”,
“vu@example.com”
);
“`

Kiểm tra:

“`csharp
a == b;
“`

Kết quả:

“`text
true
“`

Tiếp theo:

“`csharp
a.Equals(b);
“`

Kết quả:

“`text
true
“`

Nhưng:

“`csharp
ReferenceEquals(a, b);
“`

Kết quả:

“`text
false
“`

Đây là ví dụ rất tốt để ghi nhớ:

“`text
Value Equal
but
Not Same Reference
“`

—

# 41. Best Practice khi thiết kế Equality

Trước khi custom equality, nên kiểm tra:

“`text
[ ] Equality có business meaning rõ ràng không?

[ ] Nếu override Equals(), đã override GetHashCode() chưa?

[ ] Nếu overload ==, đã overload != chưa?

[ ] == và Equals() có cùng semantic không?

[ ] Property dùng để tính hash có immutable không?

[ ] Object này là Entity hay Value Object?

[ ] Record có phù hợp hơn class không?

[ ] Object có được sử dụng trong Dictionary hoặc HashSet không?
“`

—

# Kết luận

Không nên hiểu đơn giản:

“`text
== = reference
Equals = value
“`

Cách hiểu này không chính xác trong C#.

Chính xác hơn:

> `==` là equality operator và behavior phụ thuộc vào type hoặc operator overload.

Trong khi:

> `Equals()` là method dùng để biểu diễn logical equality và có thể được override.

Nếu muốn kiểm tra hai variable có trỏ tới đúng cùng một object:

“`csharp
ReferenceEquals()
“`

Nếu muốn custom equality:

“`text
Equals()
+
GetHashCode()
+
== / != nếu cần
“`

phải được thiết kế nhất quán.

Cuối cùng, chỉ cần nhớ ba dòng:

“`text
== → Equality Operator

Equals() → Logical / Value Equality

ReferenceEquals → Same Object Identity
“`

Hiểu được ba khái niệm này sẽ giúp tránh rất nhiều bug khi làm việc với:

“`text
Dictionary
HashSet
Entity
Value Object
Domain Model
LINQ
Collections
“`