C#核心面试题汇总
一、反射机制
反射允许程序在运行时动态获取类型信息并操作对象。通过Assembly类可以动态加载程序集,创建实例并调用其方法。
// 动态加载程序集并创建实例
System.Reflection.Assembly asm = System.Reflection.Assembly.LoadFrom(path);
IModule module = (IModule)asm.CreateInstance(classFullName);
module.Execute(this, groupId, parameters);
二、值类型与引用类型
2.1 值类型(System.ValueType)
值类型直接存储数据本身,包括:整数类型(int、long、short)、浮点类型(float、double、decimal)、布尔类型(bool)、字符类型(char)、结构体(struct)和枚举(enum)。值类型变量在栈上分配内存,由操作系统自动管理其生命周期,栈上的数据会随方法调用结束而自动释放。
2.2 引用类型(System.Reference)
引用类型存储的是数据的内存地址,包括:对象类型(object)、字符串(string)、数组(array)、类(class)、委托(delegate)和动态类型(dynamic)。引用类型的实际数据存储在堆内存中,由垃圾回收器(GC)负责分配和释放。
2.3 装箱与拆箱
将值类型转换为引用类型称为装箱,反之称为拆箱。这两个过程涉及类型转换和内存分配,会产生显著的性能开销。建议在实际开发中优先使用泛型集合来避免频繁的装箱拆箱操作。
三、dynamic与object的类型差异
C#中所有类型都派生自Object基类,因此任何类型的值都可以赋值给object变量,编译器会在编译阶段进行类型检查。dynamic类型则会在运行时跳过编译器的类型检查,由运行时环境解析成员访问。
public class TypeDifferenceDemo
{
public static void PrintValue(string input)
{
Console.WriteLine(input);
}
static void Main()
{
// object类型示例
object objValue = "Hello World";
// 编译时检查,object类型不能直接隐式转换为string
PrintValue(objValue); // 编译错误
PrintValue((string)objValue); // 需要显式转换
// dynamic类型示例
dynamic dynValue = "Hello World";
PrintValue(dynValue); // 正常运行,运行时解析
dynValue = 100;
PrintValue(dynValue); // 编译通过但运行时报错
}
}
四、多线程编程方案
并行处理:同一时刻多个CPU核心同时执行不同指令,从宏观和微观角度看都是真正的同时执行
并发处理:同一时间段内多个任务交替执行,宏观上看起来同时进行,微观上是CPU快速切换执行上下文
4.1 Thread 类实现
Thread是.NET平台最基础的多线程实现方式,通过创建新线程来执行特定任务。
using System;
using System.Threading;
class ThreadDemo
{
static void Main()
{
Thread worker = new Thread(ProcessTask);
worker.Start();
Console.WriteLine("主线程继续执行");
}
static void ProcessTask()
{
Console.WriteLine("工作线程执行中");
}
}
特性说明
需要开发者手动管理线程的创建、启动和终止,适用于简单的后台任务场景。
4.2 ThreadPool线程池
线程池通过复用已有线程来减少线程创建销毁的性能开销,适合执行大量短周期的异步任务。
using System;
using System.Threading;
class ThreadPoolDemo
{
static void Main()
{
ThreadPool.QueueUserWorkItem(ProcessTask);
Console.WriteLine("主线程继续执行");
}
static void ProcessTask(object state)
{
Console.WriteLine("工作线程执行中");
}
}
特性说明
自动管理线程生命周期,复用线程资源,适合高频率的小任务。
4.3 Task与Task<T>任务
Task是.NET推荐的异步编程模式,基于线程池实现,支持async/await语法糖和任务链式调用。
using System;
using System.Net.Http;
using System.Threading.Tasks;
class TaskDemo
{
static async Task Main()
{
Task task = Task.Run(ProcessTask);
await task;
Console.WriteLine("主线程继续执行");
string content = await FetchDataAsync("https://api.example.com");
Console.WriteLine(content);
}
static void ProcessTask()
{
Console.WriteLine("工作线程执行中");
}
static async Task<string> FetchDataAsync(string url)
{
using HttpClient client = new HttpClient();
return await client.GetStringAsync(url);
}
}
特性说明
完美支持异步编程模式,避免UI线程阻塞,提供更好的响应性。支持任务并行和链式调用,内部自动管理线程池,适合I/O密集型操作如网络请求和文件读写。
4.4 Parallel并行类
Parallel类提供了简洁的并行编程接口,适用于并行执行循环迭代或批量任务。
using System;
using System.Threading.Tasks;
class ParallelDemo
{
static void Main()
{
Parallel.For(0, 10, index =>
{
Console.WriteLine($"任务 {index} 正在执行");
});
Console.WriteLine("主线程继续执行");
}
}
特性说明
自动将任务分配到多个线程并行执行,特别适合数据并行处理场景,如批量数据计算和图像处理等CPU密集型任务。
4.5 BackgroundWorker组件
BackgroundWorker是专门为WinForms/WPF设计的组件,支持后台执行耗时任务,同时提供进度报告和取消机制。
using System;
using System.ComponentModel;
using System.Threading;
class BackgroundWorkerDemo
{
static void Main()
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += HandleWork;
bw.RunWorkerCompleted += HandleComplete;
bw.RunWorkerAsync();
Console.WriteLine("主线程继续执行");
}
static void HandleWork(object sender, DoWorkEventArgs e)
{
Console.WriteLine("后台任务执行中");
Thread.Sleep(2000);
}
static void HandleComplete(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("后台任务已完成");
}
}
特性说明
专为UI应用设计,支持进度更新回调和任务取消功能,适合需要与UI交互的后台操作。
4.6 Timer定时器
Timer用于实现周期性或定时执行的任务,适用于需要定期轮询的场景。
using System;
using System.Threading;
class TimerDemo
{
static void Main()
{
Timer timer = new Timer(ExecuteTask, null, 0, 1000);
Console.WriteLine("主线程继续执行");
Console.ReadLine();
}
static void ExecuteTask(object state)
{
Console.WriteLine("定时任务执行中");
}
}
特性说明
适合后台定时任务,如心跳检测、定期数据同步等场景。
4.7 PLINQ并行查询
PLINQ是LINQ的并行扩展版本,能够并行处理集合数据,充分发挥多核优势。
using System;
using System.Linq;
class PlinqDemo
{
static void Main()
{
var numbers = Enumerable.Range(1, 100);
var result = numbers.AsParallel()
.Where(n => n % 2 == 0)
.Select(n => n * n)
.ToList();
Console.WriteLine($"结果: {string.Join(", ", result)}");
}
}
特性说明
适用于大规模数据并行处理,特别适合CPU密集型的数据转换和计算场景。
五、异步编程模式
5.1 基于任务的异步模式TAP(推荐)
TAP模式自.NET Framework 4引入,是目前推荐的异步编程标准。基于Task/Task<T>类型,结合async/await关键字,提供简洁高效的异步编程体验。
// 使用Task实现并行处理
public static void ProcessParallelTasks()
{
Task job1 = Task.Run(() =>
{
Console.WriteLine("任务1完成");
});
Task job2 = Task.Run(() =>
{
Console.WriteLine("任务2完成");
});
Task<int> job3 = Task.Factory.StartNew(() =>
{
Console.WriteLine("任务3完成");
return 100;
});
Task.WaitAll(job1, job2, job3);
}
5.2 异步编程模型APM
APM是使用IAsyncResult接口实现异步操作的传统模式,需要手动编写回调函数。通过Begin/End方法对来封装异步操作。.NET Core已废弃BeginInvoke方法,不建议新项目使用。
Begin方法启动异步操作并返回IAsyncResult对象,可用于查询异步调用状态。End方法在异步操作完成后调用,获取执行结果,若操作未完成会阻塞调用线程。
public class AsyncReadDemo
{
public void ReadFileAsync(string filePath)
{
FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
byte[] buffer = new byte[1024];
fs.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(ReadCallback),
new FileReadState { Stream = fs, Buffer = buffer });
}
private class FileReadState
{
public FileStream Stream { get; set; }
public byte[] Buffer { get; set; }
}
private void ReadCallback(IAsyncResult result)
{
FileReadState state = (FileReadState)result.AsyncState;
try
{
int bytesRead = state.Stream.EndRead(result);
if (bytesRead > 0)
{
string content = Encoding.UTF8.GetString(state.Buffer, 0, bytesRead);
Console.WriteLine($"读取内容: {content}");
}
}
finally
{
state.Stream.Close();
}
}
}
5.3 基于事件的异步模式EAP
EAP模式要求方法名以Async结尾,配合事件、事件委托和EventArg派生类型使用,自动处理回调细节。该模式自.NET Framework 2.0引入,目前已被TAP模式取代。
class EventAsyncDemo
{
static void Main()
{
var worker = new DataProcessor();
worker.ProcessCompleted += OnProcessCompleted;
worker.StartProcessAsync(20);
Console.ReadLine();
}
private static void OnProcessCompleted(int result)
{
Console.WriteLine($"处理完成,结果: {result}");
}
}
public class DataProcessor : Component
{
public delegate void ProcessDelegate(int input);
public event ProcessDelegate ProcessCompleted;
public void StartProcessAsync(int input)
{
ThreadPool.QueueUserWorkItem(ProcessWork, input);
}
private void ProcessWork(object obj)
{
int input = (int)obj;
int result = input * 2;
ProcessCompleted?.Invoke(result);
}
}
六、线程同步机制
多线程环境下,为保证共享资源的安全访问,需要使用同步机制。常见的线程安全问题包括数据竞争、死锁和资源争用等。
6.1 锁机制
锁机制通过限制同时访问共享资源的线程数量来实现同步,是最常用的同步手段。
- lock关键字
lock是C#最简单的同步锁,基于Monitor类实现,语法简洁,适合临界区较小的场景。
private static readonly object syncObject = new object();
private static int count = 0;
public static void AddCount()
{
lock (syncObject)
{
count++;
}
}
- Monitor类
Monitor是lock关键字的底层实现,提供了更精细的控制能力,支持超时设置等高级功能。
private static readonly object syncObject = new object();
public static void AddCount()
{
bool acquired = false;
try
{
Monitor.Enter(syncObject, ref acquired);
count++;
}
finally
{
if (acquired)
{
Monitor.Exit(syncObject);
}
}
}
6.2 信号量(Semaphore)
信号量是一种计数锁,可以限制同时访问资源的线程数量,常用于控制数据库连接池或线程池的并发数,支持跨进程同步。
private static Semaphore semaphore = new Semaphore(2, 2);
public static void AccessResource()
{
semaphore.WaitOne();
try
{
// 访问共享资源
}
finally
{
semaphore.Release();
}
}
6.3 互斥体(Mutex)
Mutex是操作系统级别的锁,支持跨进程使用,适合多个应用程序共享同一资源的场景。由于涉及系统内核调用,性能开销较大。
查看代码``` private static Mutex mutex = new Mutex();
public static void AccessSharedResource() { mutex.WaitOne(); try { // 访问共享资源 } finally { mutex.ReleaseMutex(); } }
单实例运行示例```
static void Main(string[] args)
{
bool createdNew;
Mutex instance = new Mutex(true, "SingleInstanceMutex", out createdNew);
if (!createdNew)
{
Application.ExitThread();
return;
}
}
6.4 读写锁(ReaderWriterLock)
读写锁允许读操作并发执行,写操作独占访问。适合读多写少的场景,如缓存系统和配置管理,可以显著提升读取性能。ReaderWriterLockSlim是轻量级版本,性能更优。
private static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
private static int sharedValue = 0;
public static int ReadValue()
{
rwLock.EnterReadLock();
try
{
return sharedValue;
}
finally
{
rwLock.ExitReadLock();
}
}
public static void WriteValue(int value)
{
rwLock.EnterWriteLock();
try
{
sharedValue = value;
}
finally
{
rwLock.ExitWriteLock();
}
}
6.5 事件等待句柄
事件用于线程间信号通知,允许一个线程等待另一个线程发出信号。
- ManualResetEvent
手动重置事件,需要显式调用Reset方法关闭信号,每次Set只唤醒一个等待线程,适合一对多通知场景。
查看代码``` using System; using System.Threading;
class ManualEventDemo { private static ManualResetEvent signal = new ManualResetEvent(false);
static void Main()
{
Thread worker = new Thread(DoWork);
worker.Start();
Console.WriteLine("主线程等待信号");
signal.WaitOne();
Console.WriteLine("主线程收到信号");
worker.Join();
}
static void DoWork()
{
Console.WriteLine("工作线程执行任务");
Thread.Sleep(2000);
Console.WriteLine("工作线程发出信号");
signal.Set();
}
}
- AutoResetEvent
自动重置事件,每次Set自动重置状态,只唤醒一个等待线程。ManualResetEventSlim是轻量级版本,适合短时间等待场景。
查看代码```
using System;
using System.Threading;
class AutoEventDemo
{
private static AutoResetEvent signal = new AutoResetEvent(false);
static void Main()
{
Thread worker = new Thread(DoWork);
worker.Start();
Console.WriteLine("主线程等待信号");
signal.WaitOne();
Console.WriteLine("主线程收到信号");
worker.Join();
}
static void DoWork()
{
Console.WriteLine("工作线程执行任务");
Thread.Sleep(2000);
Console.WriteLine("工作线程发出信号");
signal.Set();
}
}
6.6 屏障(Barrier)
Barrier让多个线程在某个同步点等待,直到所有参与者都到达后才继续执行,适合分阶段并行计算场景。
private static Barrier barrier = new Barrier(3);
public static void PhaseTask()
{
Console.WriteLine("线程到达同步点");
barrier.SignalAndWait();
Console.WriteLine("线程继续执行");
}
6.7 原子操作(Interlocked)
原子操作是不可中断的最小执行单元,确保多线程环境下的数据一致性。Interlocked类提供了一系列原子操作方法,比锁机制性能更高。
private static int counter = 0;
public static void Increment()
{
Interlocked.Increment(ref counter);
}
6.8 线程安全集合
System.Collections.Concurrent命名空间提供了多种线程安全的集合类,无需手动加锁即可在多线程环境下安全使用。
ConcurrentQueue是线程安全的FIFO队列,适合多线程生产者消费者场景。
private static ConcurrentQueue<int> queue = new ConcurrentQueue<int>();
public static void Enqueue(int value)
{
queue.Enqueue(value);
}
public static bool TryDequeue(out int value)
{
return queue.TryDequeue(out value);
}
ConcurrentDictionary是线程安全的键值对集合,支持高并发的读写操作。
private static ConcurrentDictionary<string, int> dict = new ConcurrentDictionary<string, int>();
public static void AddOrUpdate(string key, int value)
{
dict.AddOrUpdate(key, value, (k, old) => old + value);
}