14 KiB
- C# 中值类型和引用类型的区别是什么? 答案:
值类型:存储数据本身,分配在栈上,通常具有较高的性能。常见的值类型有 int、float、char、struct。 引用类型:存储对数据的引用,分配在堆上。常见的引用类型有 string、class、array、delegate。 区别:
值类型变量直接存储数据,而引用类型变量存储的是指向数据的引用。 值类型赋值会复制数据,引用类型赋值会复制引用。 在堆上创建的对象的生命周期由垃圾回收器管理,而栈上的数据会在方法执行完毕后自动销毁。 2. C# 中的 static 关键字有什么作用? 答案:
静态成员:static 修饰符用于声明类的静态成员(字段、方法、属性等)。静态成员属于类本身,而不是类的实例。静态成员在程序启动时初始化,并且在整个应用程序生命周期内共享。 静态类:static 修饰符也可用于声明静态类。静态类只能包含静态成员,并且不能实例化。 示例:
public static class MyStaticClass
{
public static int Counter = 0;
public static void IncrementCounter()
{
Counter++;
}
}
- C# 中的 async 和 await 是如何工作的? 答案:
async:用于修饰方法,表示该方法是异步的。异步方法可以包含 await 关键字。 await:用于等待一个异步操作的完成。await 会暂停方法的执行,直到异步任务完成,之后恢复执行。 async 和 await 使得异步编程变得更加简洁和易读。在执行异步操作时,线程不会被阻塞,从而提高了应用的响应性。
示例:
public async Task<int> CalculateSumAsync(int a, int b)
{
await Task.Delay(1000); // 模拟异步操作
return a + b;
}
- C# 中 ref 和 out 的区别是什么? 答案:
ref:参数在传递之前必须已经初始化,方法内的修改会影响到方法外的值。 out:参数无需初始化,方法内部必须给 out 参数赋值,方法外的值将被修改。 示例:
public void RefMethod(ref int x)
{
x = x + 1; // 需要初始化
}
public void OutMethod(out int x)
{
x = 10; // 不需要初始化
}
- 什么是委托(Delegate)? 答案: 委托是对方法的引用,允许将方法作为参数传递。委托可以用来实现事件机制或回调函数。委托类似于函数指针,但它是类型安全的。
示例:
public delegate void MyDelegate(string message);
public void DisplayMessage(string message)
{
Console.WriteLine(message);
}
MyDelegate del = new MyDelegate(DisplayMessage);
del("Hello, world!");
- 解释 C# 中的事件(Event)和委托(Delegate)的关系。 答案:
委托:委托是一个引用类型,用来定义方法的签名,可以指向一个或多个方法。 事件:事件基于委托,用于通知订阅者某些事情已经发生。事件是一种封装机制,通常用于实现发布/订阅模式。 事件的主要作用是防止直接调用委托,而是通过事件的触发机制来通知订阅者。
示例:
public delegate void Notify(); // 定义委托
public class Publisher
{
public event Notify OnNotify; // 定义事件
public void TriggerEvent()
{
OnNotify?.Invoke(); // 触发事件
}
}
- C# 中的 LINQ 是什么? 答案: LINQ(Language Integrated Query)是一种用于查询集合数据的强大工具,它使得你可以用一种声明性的方法在内存中处理数据(如数组、列表、数据库等)。LINQ 提供了多种操作符,如 Where、Select、GroupBy、OrderBy 等,用来处理和筛选数据。
示例:
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = from num in numbers
where num % 2 == 0
select num;
foreach (var num in evenNumbers)
{
Console.WriteLine(num);
}
- C# 中的垃圾回收机制(Garbage Collection)是如何工作的? 答案: C# 使用自动垃圾回收(GC)机制来管理内存。GC 负责回收不再使用的对象,释放它们占用的内存空间。GC 会定期检查堆上的对象,并回收那些不再被引用的对象。C# 中的对象在堆上创建,并且它们的生命周期由垃圾回收器控制。
Generational GC:C# 使用分代垃圾回收策略,分为 3 代(0、1、2 代)。年轻代的对象容易被回收,而老年代的对象通常会存活更长时间。 Dispose 和 Finalize:实现 IDisposable 接口可以让对象在垃圾回收之前释放资源,Finalize 用于定义清理资源的逻辑。 9. C# 中的 using 语句的作用是什么? 答案: using 语句用于确保实现了 IDisposable 接口的对象在使用完毕后正确释放资源。using 语句会在代码块结束时自动调用对象的 Dispose 方法。
using (var stream = new FileStream("file.txt", FileMode.Open))
{
// 使用 stream 进行操作
} // stream.Dispose() 会自动被调用
- C# 中的扩展方法(Extension Method)是什么? 答案: 扩展方法是 C# 中的一种特殊方法,它允许向现有类型添加新功能,而无需修改原始类型的代码。扩展方法通常定义为静态方法,并且第一个参数是 this 修饰符,指示它是扩展哪个类型。
public static class StringExtensions
{
public static bool IsCapitalized(this string str)
{
return char.IsUpper(str[0]);
}
}
class Program
{
static void Main()
{
string text = "Hello";
Console.WriteLine(text.IsCapitalized()); // 使用扩展方法
}
}
垃圾回收机制(Garbage Collection)是如何工作的
在 C# 中,垃圾回收机制(Garbage Collection,简称 GC)负责自动管理内存,确保不再使用的对象能够被回收,以便释放内存并避免内存泄漏。垃圾回收是 .NET 平台的核心特性之一,它简化了内存管理,开发人员不需要手动释放内存。然而,理解其工作原理对于优化应用程序的性能和减少内存使用非常重要。
- 垃圾回收的基本概念 垃圾回收机制的主要目的是释放不再使用的内存,以便为新的对象腾出空间。GC 在运行时自动跟踪和管理托管堆中的对象,确保已经不再引用的对象能够被销毁,释放其占用的内存。
托管堆(Managed Heap) 是垃圾回收管理的内存区域,所有通过 new 关键字创建的对象(如类实例)都存储在托管堆上。托管堆是 .NET 平台自动管理的内存区域,垃圾回收器会负责清理这些对象。
- 垃圾回收的工作流程 垃圾回收的过程是由 .NET 运行时(CLR,Common Language Runtime)自动执行的,通常包括以下步骤:
2.1 标记(Mark)
垃圾回收器会首先遍历所有的活动(仍然被引用)的对象,并标记它们为存活对象。这些存活对象通过根(GC Roots)进行引用,根对象包括:
局部变量和方法参数
静态字段
当前线程的栈
其他存活对象引用的对象
通过从这些根对象开始,垃圾回收器会遍历所有直接或间接引用的对象,并标记它们为“活跃”的。
2.2 清除(Sweep)
在标记阶段完成后,垃圾回收器会遍历堆中的所有对象,检查哪些对象没有被标记为存活对象。对于这些未被标记的对象,垃圾回收器会将它们标记为“死对象”并准备删除。
2.3 压缩(Compact)
清除不再使用的对象后,垃圾回收器将释放的内存位置合并并压缩堆。这个过程包括将存活对象重新排列,使得堆中的内存空间不再被碎片化。压缩过程有时会导致对象的地址发生变化,但这是由垃圾回收器自动处理的。
- 垃圾回收的代(Generations)
垃圾回收器将堆内存分为多个代(Generation),目的是提高性能并减少回收的频率。GC 使用代的概念来优化回收过程,根据对象的生命周期将对象分配到不同的代。常见的有三个代:
3.1 第 0 代(Generation 0)
新创建的对象:大多数新对象都会被分配到第 0 代。
垃圾回收频繁:第 0 代的对象生命周期较短,因此垃圾回收器会频繁回收这一代的对象。
性能优化:垃圾回收器通过频繁清理第 0 代中的对象来减少不必要的内存占用。
3.2 第 1 代(Generation 1)
存活对象:如果一个对象在第 0 代的回收过程中仍然存活,它会被提升到第 1 代。
回收频率较低:相对于第 0 代,第 1 代的垃圾回收会更少发生,因为这些对象通常存活时间较长。
3.3 第 2 代(Generation 2)
长期存活的对象:第 2 代包含存活时间较长的对象,例如缓存对象、连接池对象等。通常较少发生垃圾回收。
回收频率最低:当第 2 代发生垃圾回收时,意味着系统正在经历较大的内存压力。
3.4 代间晋升
当对象从一个代晋升到另一个代时,它们的生命周期变得更长。GC 会尽可能将存活的对象提升到较高的代,以减少回收频率。
4. 垃圾回收的触发条件
垃圾回收的触发可以由多种因素触发:
4.1 内存压力 当系统内存不足时,垃圾回收会被触发。GC 会尝试回收不再使用的对象,以释放内存。
4.2 显式调用 开发人员可以通过调用 GC.Collect() 显式触发垃圾回收。一般来说,不建议频繁调用此方法,除非你有特别的理由进行显式垃圾回收。
GC.Collect(); // 显式触发垃圾回收
4.3 堆满 当托管堆的内存达到阈值时,也会触发垃圾回收。堆的大小通常由 .NET 运行时的配置决定,但垃圾回收器会根据需要扩展堆的大小。
- 垃圾回收的优化 虽然垃圾回收机制大大简化了内存管理,但它仍然有可能对性能产生影响,尤其是在大量对象创建和销毁时。以下是一些优化建议:
5.1 减少短生命周期的对象 尽量避免创建生命周期短暂的对象,尤其是在高频率的循环中。这些对象将频繁地进入第 0 代垃圾回收,可能导致性能问题。
5.2 使用对象池(Object Pool) 对于那些高频次创建销毁的对象,可以使用对象池(如 ObjectPool)来重复使用对象,减少垃圾回收的频率。
5.3 避免大对象分配 大对象(通常大于 85 KB)会直接分配到大对象堆(LOH,Large Object Heap),它不属于常规的代管理。大对象堆的回收较为复杂,可能导致长时间的暂停。如果可能,尽量避免频繁分配大对象。
5.4 减少内存碎片
当你处理大型数据集或长生命周期的对象时,要注意内存碎片的影响。适时压缩堆可以有效减少碎片。
- 垃圾回收的暂停时间(GC Pauses)
在垃圾回收过程中,CLR 会停止应用程序的执行,这通常称为 GC 停顿(GC Pause)。GC 停顿的时间取决于多种因素,包括堆的大小、代的数量以及是否发生了大对象堆的回收。
在较大的应用程序中,频繁的垃圾回收可能会导致性能瓶颈,尤其是在需要响应快速的实时应用中。为了减少这种停顿,.NET 提供了 后台垃圾回收(Background GC),它在后台线程中执行垃圾回收操作,以减少应用程序停顿的时间。
-
GC 类型的选择
.NET 提供了两种主要的垃圾回收模式:
7.1 工作站垃圾回收(Workstation GC)
适用于客户端应用程序(如桌面应用程序)。
在较小的系统上工作良好。
启动时有较短的暂停时间,但吞吐量较低。
7.2 服务器垃圾回收(Server GC)
适用于服务器应用程序,能够充分利用多核处理器。
增强了吞吐量和多核支持,适用于高并发、大规模应用。
启动时有较长的暂停时间,但吞吐量更高。 -
C# 定时框架 Quartz.NET(快 s .net /kwɔːts) ,使用Cron(克阮特 krɑn)表达式
-
线程池。概念 1种管理和复用线程的机制,旨在减少线程创建和销毁的开销,提高多线程应用程序的性能和资源利用率。 是1种高效的多线程管理机制,通过复用线程、减少开销和优化资源利用,提升了多线程应用程序的性能 核心:1. 线程复用:线程池维护一组预先创建的线程,任务到来时从池中分配线程执行,任务完成后线程返回池中,而不是销毁。 1. 减少开销:创建和销毁线程是昂贵的操作,线程池通过复用线程减少了这些开销。 2. 资源控制:线程池限制了同时运行的线程数量,避免系统资源被过度占用。 线程池的工作原理 1.任务队列:当任务提交到线程池时,如果所有线程都在忙碌,任务会被放入队列中等待执行。 2.线程分配:线程池中的空闲线程会从队列中取出任务并执行。 3.线程回收:任务完成后,线程不会销毁,而是返回线程池等待下一个任务。
-
线程池的优点 性能提升:减少了线程创建和销毁的开销,提高了任务执行的效率。 资源管理:限制了并发线程的数量,避免系统资源耗尽。 简化编程:开发者无需手动管理线程的生命周期,只需提交任务即可。
-
线程池的缺点 任务排队延迟:如果任务过多,可能导致任务在队列中等待时间过长。 不适合长时间任务:线程池中的线程数量有限,长时间任务可能占用线程,影响其他任务的执行。 调试困难:由于线程是复用的,调试多线程问题可能更加复杂。
-
.NET 中的线程池 在.NET中,线程池通过ThreadPool类Task.Run将任务提交到线程池。
C# 依赖注入框架 Autofac