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