C# 面试考点
C#强基础面试
1. 类的执行顺序
静态成员变量初始化 -> 静态构造函数 -> 实例成员变量初始化 -> 实例构造函数
- 如果有基类,先按上述顺序(递归地)初始化基类(从最基础的类开始,依次向下)。
- 初始化当前类的静态成员变量。
- 执行当前类的静态构造函数。
- (创建实例时)初始化当前类的实例成员变量。
- (创建实例时)执行当前类的实例构造函数。
2. 事件的多个处理器执行顺序
按事件的绑定顺序同步执行,除非处理器内部实现了异步处理
像RabbitMQ的多个消费者的Received事件的并发处理,是RabbitMQ的设计和消息队列的并发机制实现的。
3. 多线程锁有哪些,区别是什么
lock(Monitor)、Mutex、Semaphore、ReaderWriterLock、SpinLock、volatile
4. 线程安全的集合有哪些
- ConcurrentQueue
一个先进先出(FIFO)队列,提供了线程安全的Enqueue和Dequeue操作。
- ConcurrentStack
一个后进先出(LIFO)堆栈,提供了线程安全的Push和Pop操作。
- ConcurrentBag
一个允许高效并发添加和遍历元素的无序集合,不过不是按顺序访问。
- ConcurrentDictionary<TKey, TValue>
一个线程安全的哈希表,提供并发读取和写入操作,包括添加、移除和更新键值对。
- BlockingCollection
一种集合包装器,结合了生产者-消费者模式,可以在添加和获取元素时进行阻塞或限制集合大小。
5. 结构(struct)和类(class)的相同点、差异点
在.NET中,结构(struct
)和类(class
)都是数据结构,它们可以封装数据和行为(方法)。尽管它们在很多方面都是相似的,但也有一些关键的差异,这些差异影响了它们的使用和性能特性。下面是结构和类的相同点和差异:
- 相同点
- 成员:结构和类都可以包含方法、字段、属性、索引器、事件、操作符重载等成员。
- 继承:结构和类都可以实现接口。
- 构造函数:结构和类都可以定义构造函数。
- 差异
-
内存分配:
- 类是引用类型,它们的实例在托管堆上分配内存。类的变量实际上持有对对象实例内存地址的引用。
- 结构是值类型,它们的实例通常在声明它们的方法的栈上分配内存(除非它们是另一个类的成员,这种情况下它们在堆上作为那个类的一部分分配)。结构的变量直接包含它们的数据。
-
继承:
- 类可以从其他类继承,支持多态和继承链。
- 结构不能从其他结构或类继承,并且所有结构隐式地从
System.ValueType
继承,而System.ValueType
最终继承自System.Object
。
-
默认构造函数:
- 类可以没有定义任何构造函数,此时编译器会自动生成一个默认的无参构造函数。
- 结构总是有一个默认的无参构造函数,用于初始化所有值类型字段为其默认值,且不能被重写。
-
内存语义:
- 类的赋值涉及引用的复制,两个类类型的变量可以引用堆上的同一个对象实例,因此一个变量的改变会影响另一个变量。
- 结构的赋值会创建一个新的实例副本,所以一个结构变量的改变不会影响另一个。
-
装箱和拆箱:
- 类作为引用类型,不涉及装箱和拆箱操作。
- 结构作为值类型,当它们被赋值给一个
object
类型或者任何接口类型时,会发生装箱操作。相反,当一个装箱的值类型被转换回其原始类型时,会发生拆箱操作。
-
性能:
- 类的使用可能涉及到更多的堆内存分配和垃圾回收,这可能会影响性能。
- 结构由于通常在栈上分配,且值类型的赋值涉及到实例的完整复制,它们在某些场景下可能提供更好的性能。但是,如果结构很大,频繁的复制可能会导致性能下降。
-
使用场景
- 类适合用作复杂的数据结构,需要通过引用共享数据时,或当你需要利用继承和多态时。
- 结构适合用于小型、不可变的数据载体,特别是那些希望避免额外的堆分配和垃圾回收开销的场景。
选择结构还是类主要取决于你的特定需求,包括你的性能要求、数据大小、是否需要继承和多态等因素。