在 C# 中,object 类是所有类型的最终基类。它是 .NET 类型系统的根,所有值类型和引用类型都直接或间接继承自 System.Object 类(简写为 object)。
一、object 类的基本概念
- 命名空间:
System - 完整名称:
System.Object - 关键字:C# 中的
object是System.Object的别名(alias)。 - 所有类型都继承自
object:- 引用类型(如
string、class、array等) - 值类型(如
int、double、bool、struct、enum等)
- 引用类型(如
// 以下写法是等价的
object obj = "Hello";
System.Object obj2 = 123;
二、object 类的常用方法
object 类定义了一些虚方法,可以在所有类型中被重写。以下是其核心方法:
| 方法 | 说明 |
|---|---|
ToString() | 返回对象的字符串表示。建议所有类型都重写此方法。 |
Equals(object obj) | 判断当前对象是否与另一个对象相等(值相等)。 |
GetHashCode() | 返回对象的哈希码,用于哈希表等集合中。 |
GetType() | 获取对象的运行时类型(Type 对象),不可被重写。 |
MemberwiseClone() | 创建当前对象的浅拷贝(shallow copy)。 |
Finalize() | 在垃圾回收前调用,用于清理非托管资源(不建议直接重写)。 |
三、核心方法详解
1. ToString()
默认返回类型的全名,通常需要重写。
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString()
{
return $"Person: {Name}, Age: {Age}";
}
}
// 使用
Person p = new Person { Name = "Alice", Age = 30 };
Console.WriteLine(p); // 输出: Person: Alice, Age: 30
2. Equals(object obj)
用于比较两个对象是否“相等”。
public override bool Equals(object obj)
{
if (obj is Person other)
{
return Name == other.Name && Age == other.Age;
}
return false;
}
⚠️ 注意:
==运算符默认比较引用(引用类型),而Equals可以重写实现值比较。
object is Person other 是 C# 中一种结合了 类型检查 和 变量声明 的语法,属于 C# 的 模式匹配(Pattern Matching) 特性。
语法解析
if (obj is Person other)
{
// 在这个块中,other 是 Person 类型的变量
Console.WriteLine(other.Name);
}
这行代码的含义是:
obj is Person:检查obj是否可以转换为Person类型(即是否为Person的实例或其派生类的实例)。other:如果类型检查成功,自动将obj转换为Person类型,并赋值给一个新声明的变量other。- 这个
other变量只在if语句的块作用域内有效。
传统写法 vs 模式匹配写法
传统写法(需要两次操作):
if (obj is Person)
{
Person p = (Person)obj;
Console.WriteLine(p.Name);
}
或者更安全的写法:
Person p = obj as Person;
if (p != null)
{
Console.WriteLine(p.Name);
}
使用 is 模式匹配(推荐):
if (obj is Person other)
{
// 直接使用 other,类型已确定且已转换
Console.WriteLine(other.Name);
}
✅ 优点:更简洁、安全、可读性高,避免了显式类型转换和潜在的
InvalidCastException。
更简洁的变量命名
通常变量名会与类型名一致,比如:
if (obj is Person person)
{
Console.WriteLine(person.Name);
}
支持的类型
这种语法适用于:
- 类(class)
- 结构体(struct)
- 接口(interface)
- 基元类型(int, string 等)
示例:
if (obj is string str)
{
Console.WriteLine($"字符串长度: {str.Length}");
}
else if (obj is int num)
{
Console.WriteLine($"数字值: {num}");
}
C# 版本要求
is类型模式(is Type variable)从 C# 7.0 开始支持。- 如果你使用的是 .NET Core/.NET 5+ 或较新版本的 Visual Studio,通常默认支持。
注意事项
- 变量作用域:
other(或person)只在if块内有效。 - null 值处理:如果
obj为null,is Person person会返回false,不会抛异常。
Csharp编辑
object obj = null; if (obj is Person person) { // 不会进入这里 } - 可空类型:对可空值类型也适用:
object obj = 42; if (obj is int? nullableInt) { Console.WriteLine(nullableInt.Value); }
小结
object is Person other 是 C# 中一种现代、安全、高效的类型检查和转换方式:
- ✅ 一行完成类型检查 + 类型转换
- ✅ 避免强制转换异常
- ✅ 提升代码可读性和安全性
推荐在类型检查时优先使用这种模式匹配语法。
3. GetHashCode()
必须与 Equals 保持一致:如果两个对象 Equals 返回 true,它们的 GetHashCode 必须相同。
public override int GetHashCode()
{
return HashCode.Combine(Name, Age);
}
用于
Dictionary<TKey, TValue>、HashSet<T>等集合。
为什么要用 GetHashCode()?
1. 这些集合的底层是“哈希表”(Hash Table)
像 Dictionary 和 HashSet 这样的集合,为了快速查找、插入、删除元素,它们使用了一种叫 哈希表 的数据结构。
- 哈希表不是从头到尾遍历查找,而是通过一个“哈希码”(hash code)快速定位数据。
- 这个“哈希码”就是由
GetHashCode()方法提供的。
🔍 类比:就像查字典,不是一页一页翻,而是根据拼音首字母快速定位到那一部分。
GetHashCode() 的作用
每个对象都可以通过 GetHashCode() 返回一个 int 类型的数字,这个数字叫做“哈希码”。
- 哈希码不是内存地址,也不是唯一的 ID。
- 它只是一个快速标识对象内容的“指纹”。
示例:
string s1 = "hello";
string s2 = "hello";
Console.WriteLine(s1.GetHashCode()); // 比如:123456
Console.WriteLine(s2.GetHashCode()); // 同样:123456(内容相同,哈希码相同)
为什么需要重写 GetHashCode()?
问题来了:默认的 GetHashCode() 不够用!
如果你不重写 GetHashCode(),object 的默认实现可能只基于引用或简单计算,不会考虑你对象的实际内容。
举个例子:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// 两个内容完全相同的 Person
Person p1 = new Person { Name = "Alice", Age = 30 };
Person p2 = new Person { Name = "Alice", Age = 30 };
// 但它们的 GetHashCode() 可能不同!
Console.WriteLine(p1.GetHashCode()); // 比如:10001
Console.WriteLine(p2.GetHashCode()); // 比如:10002
❌ 问题:
p1和p2内容一样,但哈希码不同,集合会认为它们是“不同的”。
正确重写 GetHashCode()
为了让 Dictionary 或 HashSet 能正确识别“内容相同”的对象,我们必须重写 GetHashCode(),让它基于对象的关键字段(如 Name, Age)来计算。
使用 HashCode.Combine
public override int GetHashCode()
{
return HashCode.Combine(Name, Age);
}
它做了什么?
HashCode.Combine(...)是 .NET Core 2.1+ 提供的静态方法。- 它会自动把多个字段的哈希码组合成一个新的哈希码。
- 确保:只要
Name和Age相同,返回的哈希码就相同。
配合 Equals 方法(必须!)
GetHashCode() 必须和 Equals 方法协同工作。规则是:
✅ 如果两个对象
Equals返回true,那么它们的GetHashCode()必须相同。
所以通常要一起重写:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override bool Equals(object obj)
{
if (obj is Person other)
{
return Name == other.Name && Age == other.Age;
}
return false;
}
public override int GetHashCode()
{
return HashCode.Combine(Name, Age);
}
}
实际应用:在 HashSet<T> 中去重
var people = new HashSet<Person>();
people.Add(new Person { Name = "Alice", Age = 30 });
people.Add(new Person { Name = "Alice", Age = 30 }); // 重复
Console.WriteLine(people.Count); // 输出:1(成功去重!)
✅ 成功去重的原因:
- 第二次添加时,
HashSet会先调用GetHashCode()找“桶”。- 如果哈希码相同,再调用
Equals()判断是否真的一样。- 因为
GetHashCode和Equals都基于Name和Age,所以识别出是重复的。
在 Dictionary<TKey, TValue> 中作 Key
var dict = new Dictionary<Person, string>();
var key = new Person { Name = "Alice", Age = 30 };
dict[key] = "工程师";
// 后面用另一个“内容相同”的对象去取值
var lookupKey = new Person { Name = "Alice", Age = 30 };
Console.WriteLine(dict[lookupKey]); // 能正确输出 "工程师"
✅ 因为
GetHashCode()和Equals()正确实现了,所以能找到对应的值。
重要规则总结
| 规则 | 说明 |
|---|---|
✅ Equals 返回 true → GetHashCode 必须相同 | 否则集合会出错 |
❌ GetHashCode 相同 → Equals 不一定为 true | 允许“哈希冲突”,但会用 Equals 进一步判断 |
| 🚫 不要基于可变字段计算哈希码 | 比如 Name 被修改后,哈希码变了,对象在集合里就“找不到了” |
| ✅ 哈希码应尽量“均匀分布” | 减少冲突,提高性能 |
建议
- 如果你的类要作为
Dictionary的 Key 或放入HashSet,必须重写GetHashCode和Equals。 - 使用
HashCode.Combine(field1, field2, ...)是最简单、最推荐的方式。 - 如果字段多,可以分行写:
public override int GetHashCode() { return HashCode.Combine( FirstName, LastName, Age, Email ); }
总结
| 概念 | 作用 |
|---|---|
GetHashCode() | 返回对象的“哈希码”,用于快速定位 |
HashCode.Combine() | 把多个字段的哈希码组合成一个 |
Equals() | 判断两个对象是否“内容相等” |
Dictionary / HashSet | 依赖 GetHashCode 和 Equals 正确工作 |
✅ 简单记:要进集合,先重写 GetHashCode 和 Equals!
4. GetType()
获取对象的实际运行时类型,不可重写。
object obj = "Hello";
Console.WriteLine(obj.GetType()); // 输出: System.String
5. MemberwiseClone()
创建一个浅拷贝(只复制值类型字段,引用类型字段复制引用)。
public class Person
{
public string Name;
public Address Address;
public Person ShallowCopy()
{
return (Person)this.MemberwiseClone();
}
}
四、装箱(Boxing)与拆箱(Unboxing)
由于 object 是所有类型的基类,C# 支持将值类型自动转换为 object(装箱),反之为拆箱。
int i = 123;
object obj = i; // 装箱:值类型 → 引用类型
int j = (int)obj; // 拆箱:引用类型 → 值类型
⚠️ 装箱/拆箱有性能开销,应尽量避免频繁使用。
五、object 的用途
- 通用容器:如
ArrayList、object[]等可存储任意类型。 - 多态性:方法参数使用
object可接受任何类型。 - 反射:通过
GetType()获取类型信息。 - 事件处理:
EventHandler中的sender参数通常是object。 - JSON 序列化/反序列化:常以
object作为通用数据类型。
六、最佳实践
- 重写
Equals、GetHashCode、ToString以提供更有意义的行为。 - 避免过度使用
object,优先使用泛型(如List<T>而不是ArrayList)以提高类型安全和性能。 - 在需要通用性时使用
object,但注意装箱/拆箱问题。
总结
object 是 C# 类型系统的基石,理解其方法和行为对于编写健壮、高效的 C# 代码至关重要。它是多态、反射、集合操作等高级特性的基础。
Dictionary 的基本结构
Dictionary<TKey, TValue> 中:
TKey:键的类型(如string,int,Guid等)TValue:值的类型(可以是任何类型,如string,int,Person,List<T>等)
示例:
// 创建一个字典:用 string 作为键,int 作为值
Dictionary<string, int> ages = new Dictionary<string, int>();
// 添加数据
ages["Alice"] = 30;
ages["Bob"] = 25;
ages["Charlie"] = 35;
// 查找数据
int aliceAge = ages["Alice"]; // 返回 30
Dictionary 的核心作用
| 作用 | 说明 |
|---|---|
| ✅ 快速查找 | 通过 Key 可以在 接近 O(1) 时间复杂度 内找到对应的 Value(非常快!) |
| ✅ 避免重复的 Key | 每个 Key 在 Dictionary 中必须是唯一的,重复添加会抛异常或覆盖(取决于方法) |
| ✅ 灵活存储结构化数据 | 可以把复杂对象作为 Value,实现“配置”、“缓存”、“映射表”等功能 |
实际应用场景
1. 缓存数据
// 缓存用户信息,避免重复查询数据库
Dictionary<int, User> userCache = new Dictionary<int, User>();
User user = userCache[123]; // 通过用户ID快速获取
2. 配置映射
// 配置错误码对应的提示信息
Dictionary<int, string> errorMessages = new Dictionary<int, string>
{
{ 404, "页面未找到" },
{ 500, "服务器内部错误" },
{ 403, "权限不足" }
};
string msg = errorMessages[404]; // 返回 "页面未找到"
3. 统计计数
// 统计单词出现次数
Dictionary<string, int> wordCount = new Dictionary<string, int>();
foreach (string word in words)
{
if (wordCount.ContainsKey(word))
wordCount[word]++;
else
wordCount[word] = 1;
}
目标:统计每个单词出现了多少次
比如有这些单词:
"apple", "banana", "apple", "orange", "banana", "apple"
我们希望得到的结果是:
- "apple" 出现了 3 次
- "banana" 出现了 2 次
- "orange" 出现了 1 次
🔧 代码逐行解析
// 1. 创建一个“字典”来保存:单词 → 次数 的映射
Dictionary<string, int> wordCount = new Dictionary<string, int>();
Dictionary<string, int>:意思是这个字典的 Key 是字符串(单词),Value 是整数(次数)wordCount:是这个字典的名字- 刚开始它是空的,什么都没有
// 2. 遍历每一个单词
foreach (string word in words)
{
// 每次循环,word 就是当前处理的单词
// 比如第一次是 "apple",第二次是 "banana",第三次又是 "apple"...
}
words是一个字符串数组或列表,比如:string[] words = { "apple", "banana", "apple", "orange", "banana", "apple" };
现在进入循环体,这是最关键的逻辑:
if (wordCount.ContainsKey(word))
wordCount[word]++;
else
wordCount[word] = 1;
我们用一个表格来模拟整个过程:
当前单词 word | wordCount 字典里有没有这个单词? | 执行的操作 | wordCount 变成什么 |
|---|---|---|---|
| "apple" | ❌ 没有(第一次出现) | else 分支:wordCount["apple"] = 1 | { "apple": 1 } |
| "banana" | ❌ 没有(第一次出现) | else 分支:wordCount["banana"] = 1 | { "apple": 1, "banana": 1 } |
| "apple" | ✅ 有!(之前存过) | if 分支:wordCount["apple"]++(从 1 变成 2) | { "apple": 2, "banana": 1 } |
| "orange" | ❌ 没有(第一次出现) | else 分支:wordCount["orange"] = 1 | { "apple": 2, "banana": 1, "orange": 1 } |
| "banana" | ✅ 有!(之前存过) | if 分支:wordCount["banana"]++(从 1 变成 2) | { "apple": 2, "banana": 2, "orange": 1 } |
| "apple" | ✅ 有!(之前存过) | if 分支:wordCount["apple"]++(从 2 变成 3) | { "apple": 3, "banana": 2, "orange": 1 } |
✅ 最终结果
wordCount["apple"] // 返回 3
wordCount["banana"] // 返回 2
wordCount["orange"] // 返回 1
完美达成了统计目标!
🔍 关键点解释
| 代码 | 含义 |
|---|---|
wordCount.ContainsKey(word) | 问字典:“你有没有存过这个单词?” |
wordCount[word]++ | 如果有,就把它的计数 加 1 |
wordCount[word] = 1 | 如果没有,就第一次记录,次数设为 1 |
💡 更简洁的写法(C# 7+ 推荐)
上面的 if-else 可以用 TryGetValue 写得更高效:
foreach (string word in words)
{
wordCount.TryGetValue(word, out int count);
wordCount[word] = count + 1;
}
或者用 C# 9+ 的 ?? 操作符:
foreach (string word in words)
{
wordCount[word] = wordCount.GetValueOrDefault(word) + 1;
}
这些写法只查找一次,性能更好。
🧠 类比理解
想象你有一个空的记事本(wordCount),然后你读一本书,每看到一个单词:
- 看看记事本里有没有记过这个词
- 如果有,就在原来的数字上 加 1
- 如果没有,就新写一行,写上单词和数字 1
这就是 Dictionary 在做的事情!
✅ 总结一句话:
这段代码就是用一个“单词 → 次数”的字典,每遇到一个单词,就检查它是否已经记录过,如果记录过就加 1,否则从 1 开始记录,最终实现统计。
4. 状态机或行为映射
// 根据命令字符串执行不同方法
Dictionary<string, Action> commands = new Dictionary<string, Action>
{
{ "start", Start },
{ "stop", Stop },
{ "pause", Pause }
};
commands["start"](); // 调用 Start() 方法
常用方法
| 方法 | 说明 |
|---|---|
dict[key] = value | 添加或更新键值对 |
dict[key] | 获取指定键的值(键不存在会抛异常) |
dict.TryGetValue(key, out value) | 安全获取值,键不存在返回 false |
dict.ContainsKey(key) | 判断是否包含某个键 |
dict.Remove(key) | 删除指定键 |
dict.Clear() | 清空所有数据 |
dict.Count | 获取键值对的数量 |
推荐用法:TryGetValue
if (ages.TryGetValue("Alice", out int age))
{
Console.WriteLine($"Alice 的年龄是 {age}");
}
else
{
Console.WriteLine("找不到 Alice");
}
✅ 比先
ContainsKey再取值更高效,只查找一次。
Dictionary 与 List 的对比
| 特性 | List<T> | Dictionary<TKey, TValue> |
|---|---|---|
| 查找方式 | 通过索引(如 list[0])或遍历查找 | 通过键(如 dict["name"]) |
| 查找速度 | O(n)(遍历)或 O(1)(索引) | O(1)(平均) |
| 是否允许重复 Key | 允许重复元素 | 不允许重复 Key |
| 适用场景 | 有序列表、需要索引访问 | 快速查找、映射、缓存 |
注意事项
- Key 不能为 null(除非 TKey 是可空类型)
- 同一个 Key 只能出现一次,重复添加会抛
ArgumentException - Key 的类型最好重写
GetHashCode和Equals(如自定义类作 Key) - 线程不安全:多线程读写需要加锁或使用
ConcurrentDictionary
总结
Dictionary<TKey, TValue> 的作用就是:
🔑 用一个“键”快速找到对应的“值”,就像查字典一样。
它适用于:
- 需要快速查找的场景
- 数据有唯一标识(Key) 的场景
- 实现缓存、映射、配置、统计等功能
✅ 记住:如果你需要“通过某个 ID 或名字快速找到数据”,就用 Dictionary!
669

被折叠的 条评论
为什么被折叠?



