这里写目录标题
- 目录
- 1. 什么情况下需要override Equals?
- 2. 什么时候需要override GetHashCode?
- 3. `int`和`int?`有什么关系?两个类型转换是否涉及装箱过程?
- 4. `String`类和`StringBuiler`类有什么区别?
- 5. 抽象类和接口有什么区别?
- 6.委托和事件的区别是什么?
- 7. 值类型与引用类型有什么区别?
- 8. 什么是装箱和拆箱?
- 9. 什么情况使用结构(struct)?
- 10. new关键字有哪些作用?
- 11. 实例化类的执行顺序是什么?
- 12. 为什么不能在基类中调用虚方法?
- 13. 重载与重写(`override`)的区别?
- 14. `static`关键字有哪些作用?
- 15.`const`和`readonly`有什么区别?
- 16. as和is的区别是什么?
- 17. 什么对象可以被foreach遍历?
- 18. 什么是序列化与反序列化?
- 19. 什么是浅拷贝与深拷贝?
目录
1. 什么情况下需要override Equals?
默认情况下,C#会使用Object.Equals
方法进行比较,该方法对于值类型比较的是值,对于引用类型比较的是引用。
这可能不符合预期,所以在以下情况需要进行重写Equals
方法:
- 引用类型的业务逻辑比较:对于类(class)类型,如果希望基于业务逻辑而非引用相等性来比较两个对象实例,应该重写
Equals
方法。例如,两个用户对象可能具有不同的属性集合,但在某些情况下,即使它们不是同一个实例,也可能被视为相等。 - 配合集合使用:当你的对象将被用作字典(Dictionary)或哈希集合(HashSet)的键时,需要确保这些对象的
Equals
和GetHashCode
方法被适当地重写,以保证正确的键查找行为;
2. 什么时候需要override GetHashCode?
- 使用哈希集合:当类被用作哈希集合(如
HashSet
或Dictionary
等)的键时,必须提供一个合适的GetHashCode
实现,以确保对象能够正确地存储和检索。 - 配合Equals方法:如果已经重写了
Equals
方法来定义两个对象相等的逻辑,那么也应该重写GetHashCode
方法,以保持Equals
和GetHashCode
的一致性。这是因为两个相等的对象必须返回相同的哈希值(否则在资源字典中,将有可能成功地添加两个相等的对象,而在查找时又找不到对象)
3. int
和int?
有什么关系?两个类型转换是否涉及装箱过程?
int
是一个基本的整型数据类型,它不允许赋值为null
。int?
则是Nullable<int>
的简写,表示可空的整型,可以赋值为任何整数值,也可以赋值为null
;int
和Nullable<int>
都是值类型,两个类型转化不涉及装箱过程
4. String
类和StringBuiler
类有什么区别?
- String类:
String
是C#中不可变的字符序列,一旦创建了一个String
对象,就不能更改其内容。每一次对String
的操作,如拼接或修改,实际上都会创建一个新的String
实例; - StringBuilder类:
StringBuilder
是一个可变的字符容器,可以在原有对象的基础上进行修改,如添加、删除和替换字符,而不会创建新的对象。它是为了解决String
在频繁修改时的低效率问题而设计的。因此,当需要对字符串进行大量的修改操作时,使用StringBuilder
比使用String
更加高效。
5. 抽象类和接口有什么区别?
- 继承特点:一个类只可以继承一个抽象类,但可以继承多个接口;
- 方法实现:当一个类继承了一个抽象类,它只需要实现抽象类中的所有抽象方法(包括抽象属性),而其他方法和属性可以不重新实现;当一个类继承了一个接口,它必须实现接口定义的所有方法;
- 成员类型:抽象类可以有构造函数,接口不能有;抽象类可以有字段,接口不能有;抽象类的成员可以是私有的,接口成员不能是私有的;
- 设计目的:抽象类通常是为了为其他类提供一个共同的基础框架,为提炼共同实现而设计;接口为不同的类提供了一个共同遵循的标准,为了抽象。
6.委托和事件的区别是什么?
委托是一个类,其实例对象持有一个或多个方法。
委托的特点:
- 使用方式:可以通过赋值运算符
=
或+=
来注册方法,使用-=
来注销方法。可以直接被调用,以触发所引用的方法执行; - 使用场景:在调用方法前,某个关键部分处理方法不确定,可将委托作为参数(如LINQ查询);在异步编程中,委托常用于定义回调方法,以便在异步操作完成时执行特定的代码。
事件封装了委托,提供了更严格的访问控制,是委托的上层建筑。
- 使用方式:只能通过
+=
来注册方法和通过-=
来注销方法。事件的触发通常由事件源负责,而不是由外部直接调用; - 使用场景:实现发布-订阅模式;防止委托的滥用;
7. 值类型与引用类型有什么区别?
值类型和引用类型是C#中两种不同的数据类型。
值类型:
- 组成:基本数据类型(int、float、double、char等)和结构类型(struct);
- 存储:数据通常存放在栈;当被包含在类中则存储在堆上;
- 赋值:当值类型变量被赋值给另一个变量时,会进行值复制,这意味着两个变量将拥有各自的副本,对一个变量的修改不会影响另一个;
- 大小:实例大小不大于16字节。
引用类型
- 组成:所有类(Class)、数组(Array) 和委托(Delegate) 等;
- 存储:引用类型需要两段内存,第一段存储实际数据,位于堆;第二段存储引用,用来指向数据在堆中的存储位置,位于栈;
- 赋值:当引用类型变量被赋值给另一个变量时,只复制引用,因此两个变量将指向内存中的同一个对象,对一个变量的修改会影响到另一个。
- 大小:实例大小可以大于16字节。
8. 什么是装箱和拆箱?
装箱和拆箱是值类型与引用类型之间转换时发生的操作。
- 装箱(Boxing):是将值类型转换为引用类型的一个过程。装箱时,值类型实例被包裹在一个新的对象中,并分配在堆上,这样它就有了一个引用地址,可以作为对象使用。
- 拆箱(Unboxing):是将引用类型转换回原来的值类型。拆箱时,从堆上的对象中提取出值类型数据,并将其复制到一个栈上的临时位置,以便作为值类型使用。
9. 什么情况使用结构(struct)?
必要条件:
- 实例大小不大于16字节(大于16字节的struct通常需要在堆上分配)
- 值类型语义
- 逻辑上表示单个值
- 不必频繁装箱
使用struct
的优点:
- 性能优化:当需要创建和销毁对象的频率很高时,
struct
可以提供更好的性能; - 内存优化:使用
struct
可以减少内存的使用,因为struct
通常占用的内存比class
少。 - 值类型语义:如果希望类型具有值语义,即通过值传递而不是引用传递,那么应该使用struct。这有助于确保类型在赋值和作为参数传递时的完整性和独立性。
10. new关键字有哪些作用?
- 创建对象实例:使用
new
关键字可以创建一个类或结构的新实例。 - 隐藏基类成员:使用
new
隐藏基类成员是一种避免多态行为的方式,它使得派生类中的新实现仅在明确通过派生类类型调用时才会被执行(否则,只要是派生实例(哪怕它以父类身份出现),都会调用子类的方法)。
11. 实例化类的执行顺序是什么?
不考虑继承关系:
- 静态字段
- 静态构造方法
- 实例字段
- 实例构造方法
考虑继承关系
- 子类的静态字段
- 子类的静态构造方法
- 子类的实例字段
- 父类的静态字段
- 父类的静态构造方法
- 父类的实例字段
- 父类的实例构造方法
- 子类的实例构造方法
12. 为什么不能在基类中调用虚方法?
在执行基类的虚方法会调用派生类的覆写方法,而派生类的构造函数尚未开始调用,从而导致在派生类未完全构造之前就已经发生了覆写方法的调用。
13. 重载与重写(override
)的区别?
重载:发生在同一个类或接口中,重载的方法函数名相同,但函数签名(由名称和参数列表组成,不包括返回类型)不同;重载的方法可以有不同的访问权限;
重写:发生在继承体系中,当子类需要提供父类方法的另一种实现时使用,支持多态;重写的方法可访问性必须和基类相同。
14. static
关键字有哪些作用?
- 类级别成员:
static
关键字用于声明类级别的成员,这意味着这些成员属于类本身,而不是类的实例。因此,它们可以在没有创建类的实例的情况下访问; - 共享同一个值:
static
成员只有一份存储空间,所有实例共享这个static
成员的值; - 静态构造函数:可以用于声明静态构造函数,它在类加载到内存时执行,通常用于初始化静态数据或执行仅需要执行一次的操作;
15.const
和readonly
有什么区别?
- const:
const
关键字用于声明一个值永远不变的字段。这意味着const
字段必须在声明时初始化,其值是在编译时确定,不能被修改。const
只能声明一个简单类型、枚举类型、或者字符串类型; - readonly:
readonly
关键字用于声明一个字段,该字段只能在声明时或构造函数中初始化,并且在对象的整个生命周期内保持不变。与const
不同,readonly
字段可以在运行时初始化,只要它们是在构造函数中完成的。readonly
字段对于引用类型来说,意味着引用本身是只读的,但引用的对象(如果是可变的)可以改变。
16. as和is的区别是什么?
- as:
as
关键字用于执行安全的类型转换,即在尝试将一个表达式转换为特定类型时不会抛出异常。如果转换成功,as返回转换后的值;如果转换失败,则返回null
;as
运算符只适用于可以为null
的类型。 - is:
is
关键字用于检查对象是否为特定类型的实例,或者该对象是否与指定类型兼容。is
操作符返回一个布尔值,如果对象是指定的类型或其派生类型,则返回true
;否则返回false
。is
操作符在判断类型时不会进行实际的类型转换,并且不会抛出异常。
17. 什么对象可以被foreach遍历?
任何实现了IEnumerable
或IEnumerable<T>
接口的类型.
18. 什么是序列化与反序列化?
序列化是将对象的状态信息转换为可以存储或传输的形式的过程,而反序列化则是将这种形式的数据恢复为对象的过程。
19. 什么是浅拷贝与深拷贝?
浅拷贝(Shallow Copy)是指创建一个新对象,并且复制原始对象的引用类型成员的引用,而不复制引用类型的成员对象本身。浅拷贝后,原对象和拷贝对象的成员变量指向同一个内存地址。
深拷贝(Deep Copy)则是指创建一个新对象,并递归地复制原始对象的所有成员及其子对象,直到所有层级的对象都被复制。深拷贝后,原对象和拷贝对象的成员变量指向不同的内存地址。