C#核心学习(十一)面向对象--继承(2):万物之父object和装箱拆箱

发布于:2025-04-09 ⋅ 阅读:(37) ⋅ 点赞:(0)

        目录

一、C#中的万物之父object是什么?

二、object类提供的基本方法与用途

1. 核心方法

1. ​什么是哈希码?

2. ​默认行为

2. 基类的用途

三、装箱(Boxing)与拆箱(Unboxing)

1. 装箱(Boxing)

2. 拆箱(Unboxing)

1. ​值类型装箱后修改

2. ​引用类型装箱后修改

四、装箱与拆箱的优缺点

优点

缺点

五、如何避免装箱与拆箱?

总结


        相信读者看了上一篇文章之后,大概了解了什么是里氏替换原则,今天我们就来看看在C#中所有类的父类是谁!

一、C#中的万物之父object是什么?

        在C#中,object类型是所有类型的基类​(基类即父类)。无论是值类型(如intdoublestruct)还是引用类型(如string、自定义类),最终都直接或间接继承自object。这意味着,任何变量都可以被隐式或显式地转换为object类型。

int num = 42;
object obj = num;  // int类型隐式转换为object

  object的存在为C#提供了类型统一性的底层支持,使得开发者可以通过基类引用操作任意类型的对象,这在实现多态、泛型等特性时尤为重要。 

万物之父
关键字object
概念:
object是所有类型的基类,他是一个类(引用类型)
作用:
1.可以利用里氏替换原则,用object容器装所有对象
2.可以用来表示不确定类型,作为函数参数类型

二、object类提供的基本方法与用途

1. 核心方法

object类为所有类型提供了以下基础方法:

  • ToString():返回对象的字符串表示。默认返回类型的全名(如System.Int32),但通常会被子类重写(如int重写后返回数字本身)。

示例:

int num = 100;
Console.WriteLine(num.ToString());  // 输出 "100"(int重写了ToString)

Person p = new Person { Name = "Alice" };
Console.WriteLine(p.ToString());     // 输出 "Person"(未重写时)
// 重写后:
public class Person {
    public string Name { get; set; }
    public override string ToString() => $"Name: {Name}";
}
// 输出 "Name: Alice"
  • Equals(object obj):判断对象是否相等。默认比较引用(对引用类型),值类型需重写以实现值比较。这里的值重写是指什么呢,我思考的是取掉本身某一些不需要比较的逻辑,添加属于自己特殊的比较方式。例如你有一个圆的类,你想比较每个圆面积的大小,这个时候就可以重写这个函数,实现某些特殊需求。

示例:

int x = 5, y = 5;
Console.WriteLine(x.Equals(y));      // True(值类型比较值)

object obj1 = x, obj2 = y;
Console.WriteLine(obj1.Equals(obj2)); // True(装箱后值仍相等)

Person p1 = new Person { Name = "Bob" };
Person p2 = new Person { Name = "Bob" };
Console.WriteLine(p1.Equals(p2));    // False(引用不同,除非重写Equals)
//比较地址哈
  • GetType():返回对象的运行时类型(Type对象),用于反射。方法返回对象的运行时类型​(即实际类型)后面当我们学习到反射时 我们还会见面的!

示例:

object obj = "Hello";
Type type = obj.GetType();
Console.WriteLine(type.Name);        // 输出 "String"
Console.WriteLine(type.IsValueType); // 输出 False
  • GetHashCode():生成对象的哈希码,用于哈希表(如Dictionary)中的快速查找。

补充知识:

1. ​什么是哈希码?
  • 哈希码(Hash Code)​ 是一个由对象内容生成的整数值,用于在哈希表(如 DictionaryHashSet)中快速定位数据。
  • 规则
    • 若两个对象相等(Equals 返回 true),它们的哈希码必须相同
    • 哈希码应尽量均匀分布,减少哈希冲突。
2. ​默认行为
  • 引用类型:默认基于对象地址生成哈希码(即使内容相同,不同实例的哈希码也不同)。
  • 值类型:默认基于字段值生成哈希码。

        哈希码就相当于每个对象或者值的身份证,不过不同的是可能会有哈希冲突(多个人对应一个身份证)。身份证肯定每人一个,有了哈希码可以干什么呢,当然是查询啦!哈希表可以非常快速的查询某一个元素,就像班级的点名表一样,老师只要挨着挨着点,就会发现那些同学今天翘课了

string s1 = "A";
string s2 = "A";
Console.WriteLine(s1.GetHashCode() == s2.GetHashCode()); // True

        你也可以自己重写生成对象哈希码函数,毕竟这是系统写的,他能写,咱也能写。只不过没他写得好罢了,中心思想就是一个尽可能减少哈希冲突。 

示例:

public class Person {
    public string Name { get; set; }
    public int Age { get; set; }

    public override int GetHashCode() {
        // 组合字段生成哈希码
        return Name.GetHashCode() ^ Age.GetHashCode();
    }

    public override bool Equals(object obj) {
        if (obj is Person other) {
            return Name == other.Name && Age == other.Age;
        }
        return false;
    }
}

// 使用示例
Person p1 = new Person { Name = "Alice", Age = 30 };
Person p2 = new Person { Name = "Alice", Age = 30 };
Console.WriteLine(p1.GetHashCode() == p2.GetHashCode());  // 输出 True(已重写)

2. 基类的用途

  • 多态(Polymorphism)​

    • 如何通过virtualoverride关键字实现方法重写。
    • 接口(interface)如何扩展多态行为。
    • 学习章节:面向对象编程(OOP)进阶。
  • 泛型容器(Generic Collections)​

    • 为什么List<T>ArrayList更高效且类型安全。
    • 泛型约束(where T : class)的设计模式。
    • 学习章节:集合与泛型编程。
  • 反射(Reflection)​

    • 如何通过TypeActivator动态创建对象。
    • 反射在框架开发(如依赖注入)中的应用。
    • 学习章节:高级C#特性与反射机制。

三、装箱(Boxing)与拆箱(Unboxing)

补充知识:

在C#中,​是两种不同的内存区域,用于存储程序运行时的数据:

  1. 栈(Stack)​

    • 特点:由系统自动分配和释放,内存大小固定,存取速度快。小
    • 存储内容
      • 值类型变量(如intdoublestruct)。
      • 方法调用的上下文(如局部变量、参数、返回地址)。
    • 生命周期:变量在方法执行时创建,方法结束后自动销毁。
  2. 堆(Heap)​

    • 特点:由开发者或垃圾回收器(GC)管理,内存动态分配,存取速度较慢。大
    • 存储内容
      • 引用类型对象(如stringclass实例)。
      • 被装箱的值类型(即object包裹的值类型)。
    • 生命周期:对象在new时创建,由GC在无引用时回收。

        注意哈,这里的堆和栈是和数据结构中的堆和栈不一样哈,那是一种特殊的数据结构,这里是计算机的内存分布 

为什么装箱涉及堆?
        值类型原本在栈上,但装箱时会将其复制到堆中,以便通过object引用统一操作。

1. 装箱(Boxing)

定义:将值类型转换为object引用类型的过程。
过程

  1. 在堆(Heap)中分配内存,用于存储值类型的副本。
  2. 将栈(Stack)中的值类型数据复制到堆中的新对象。
  3. 返回新对象的引用。
int value = 100;
object boxed = value;  // 装箱发生!

发生条件
用object 存值类型(装箱)
再把object 转为值类型(拆箱)

装箱
把值类型用引用类型存储
栈内存会迁移到堆内存中

2. 拆箱(Unboxing)

定义:将object引用类型转换回原始值类型的过程。
过程

  1. 检查object引用是否为目标值类型的装箱实例。
  2. 将堆中的数据复制回栈中的值类型变量。
int unboxed = (int)boxed;  // 拆箱成功(类型匹配)
// int error = (short)boxed;  // 运行时错误:类型不匹配

补充知识点:装箱过程中值类型和引用类型的变化: 

1. ​值类型装箱后修改

        值类型装箱时会创建副本,因此修改原始值不会影响已装箱的副本,反之亦然。

示例:

int original = 10;
object boxed = original;  // 装箱,创建副本

original = 20;            // 修改原始值
Console.WriteLine(original);    // 输出 20
Console.WriteLine((int)boxed);  // 输出 10(未受影响)注意喔这里也是拆箱哦

// 尝试修改装箱后的值(需先拆箱)
int unboxed = (int)boxed;
unboxed = 30;
Console.WriteLine(unboxed);      // 输出 30
Console.WriteLine((int)boxed);   // 输出 10(仍不受影响)
2. ​引用类型装箱后修改

        引用类型装箱时仅复制引用(即“装箱”在语义上无实际操作),因此修改对象内容会影响所有引用。

public class Data {
    public int Value { get; set; }
}

Data original = new Data { Value = 10 };
object boxed = original;  // 装箱(仅复制引用)

original.Value = 20;      // 修改原对象的属性
Console.WriteLine(original.Value);    // 输出 20
Console.WriteLine(((Data)boxed).Value); // 输出 20(指向同一对象)

((Data)boxed).Value = 30;             // 修改装箱后的对象
Console.WriteLine(original.Value);    // 输出 30(同步变化)
  • 值类型:装箱后与原数据完全独立,修改互不影响。
  • 引用类型:装箱后与原数据指向同一对象,修改会同步。

四、装箱与拆箱的优缺点

优点

  • 灵活性:允许值类型在需要引用类型的场景中使用(如非泛型集合)。
  • 兼容性:为遗留代码(如.NET 1.0的非泛型集合)提供支持。

缺点

  • 性能损耗
    • 装箱:涉及堆内存分配和数据复制,耗时约为20ns(纳秒)。
    • 拆箱:需类型检查,数据从堆复制回栈,耗时约为10ns。
  • 类型安全风险:拆箱时若类型不匹配会抛出InvalidCastException
// 示例:大量装箱引发的性能问题
ArrayList array = new ArrayList();
for (int i = 0; i < 100000; i++) {
    array.Add(i);  // 每次Add都会装箱!
}

五、如何避免装箱与拆箱?

  1. 使用泛型集合:如List<T>替代ArrayList
  2. 优先使用接口:例如用IEquatable<T>避免值类型比较时的装箱。
  3. ​**object类型谨慎转型**:尽量通过泛型或is/as操作符确保类型安全。
// 使用泛型集合避免装箱
List<int> genericList = new List<int>();
genericList.Add(42);  // 无装箱!

总结

  1. object是类型系统的基石:所有类型隐式继承object,支持多态和类型统一。
  2. 装箱拆箱需慎用:值类型与object互转会带来性能开销,高频场景优先选择泛型。
  3. 方法重写是关键:合理重写ToString()Equals()GetHashCode()可提升代码可读性与性能。
  4. 堆栈差异影响行为:值类型装箱后独立存在堆中,引用类型装箱仍共享同一对象。

一起加油吧!