类型 · 比较

C# 里 == 与 .Equals()

在多数新手代码里它们一致——而差异恰恰是 bug 藏身处:null 处理,以及每种类型对"相等"的定义。

对基元类型:行为完全一致

int x = 5;
int y = 5;
bool f = false;

Console.WriteLine(x == y);      // 值比较
Console.WriteLine(x.Equals(y)); // 对 int 来说同一个答案
Console.WriteLine(f.Equals(false));

对 int、double、bool、char 没有实质区别——用 ==,读起来更顺。

对字符串:结果相同,机制不同

string a = "apple";

Console.WriteLine(a == "apple");     // True  —— C# 让 == 对字符串比较内容
Console.WriteLine(a == "Apple");     // False —— 大小写始终敏感
Console.WriteLine(a.Equals("apple"));   // True  —— 同样是内容
Console.WriteLine(a.Equals("Apple"));   // False —— 同样的大小写规则

两者都比字符内容,两者都区分大小写。(在其他语言里 == 对字符串比较引用——经典的 C#/Java 混淆;C# 把运算符重载成了"有用的那个"含义。)

差异一:null 处理

真正的行为分水岭。== 对 null 从容;对 null 调用方法直接爆炸:

string s = null;

// == 是静态检查——任何一边是 null 都没事
Console.WriteLine(s == null);

// .Equals() 是对 s 的方法调用——s 是 null,所以先加护栏:
if (s != null)
{
    Console.WriteLine(s.Equals("x"));
}
else
{
    Console.WriteLine("safe");   // 走的是这里——直接 s.Equals 会抛异常
}

实用规则:任何一边可能为 null 时,用 ==(或 string.IsNullOrEmpty),别用 .Equals()。

差异二:"相等"的定义可以被重写

Equals() 是一个类可以重定义的方法;== 是一个类可能重载的运算符。对你自己的类,两者默认都不做内容比较——都是身份(struct vs class 一文里 a == b → False 的演示):

class Dog { public string Name; }

Dog d1 = new Dog { Name = "Rex" };
Dog d2 = new Dog { Name = "Rex" };
Console.WriteLine(d1 == d2);        // False —— 不同对象
Console.WriteLine(d1.Equals(d2));   // False —— 同样,除非重写 Equals

record(C# 9+)和 struct 会把默认值改成按值比较——这正是数据类型纷纷改用 record 的原因之一。

日常赢家:忽略大小写的比较

用户输入不会尊重你的大小写。地道工具不是 == 也不是裸 Equals,而是带比较类型的 Equals(Visual Studio 里;浏览器引擎还没有这个重载):

string input = "YES";

// 这三行是真实世界的模式:
input == "yes"                                        // False —— 大小写敏感
input.Equals("yes", StringComparison.OrdinalIgnoreCase)   // True
input.ToLower() == "yes"                              // True —— 快糙版

优先 StringComparison.OrdinalIgnoreCase 而不是 ToLower() 链——它不分配内存,而且意图摆在脸上。

决策表

比较什么用
数字、bool、char==
字符串,大小写敏感==
字符串,忽略大小写Equals(…, OrdinalIgnoreCase)
任何可能为 null 的东西==(或 IsNullOrEmpty)
自己的内容相等类型做成 record,或重写 Equals