C#初级面试题21~30道详解:从基础语法到多线程

📅 2026/8/22 16:50:45
C#初级面试题21~30道详解:从基础语法到多线程
1. C#初级面试题21~30道详解作为.NET技术栈的核心语言C#在软件开发领域占据着重要地位。对于初级开发者而言掌握基础概念和常见问题的解答思路至关重要。本系列将深入解析21~30道高频面试题这些问题涵盖了从基础语法到常用框架的核心知识点。2. 基础概念与类型系统2.1 值类型与引用类型的区别C#中的数据类型主要分为值类型和引用类型它们的存储方式和行为有本质区别存储位置值类型存储在栈(stack)上引用类型存储在堆(heap)上赋值行为值类型赋值是复制整个值引用类型赋值是复制引用默认值值类型各字段初始化为0/false引用类型初始化为null内存管理值类型超出作用域自动释放引用类型由GC管理实际开发中最容易混淆的是结构体(struct)和类(class)的选择。结构体是值类型适合小型、不可变的数据结构类是引用类型适合需要继承和多态的场景。2.2 装箱与拆箱操作装箱(boxing)和拆箱(unboxing)是值类型与引用类型相互转换的过程int i 123; // 值类型 object o i; // 装箱 - 值类型转为引用类型 int j (int)o; // 拆箱 - 引用类型转回值类型装箱操作会在堆上分配内存并复制值拆箱则需要类型检查。频繁的装箱拆箱会影响性能在循环或高性能场景应尽量避免。提示使用泛型集合(如List )可以避免值类型的装箱操作相比ArrayList能显著提升性能。3. 面向对象特性3.1 封装、继承与多态C#作为面向对象语言三大特性在实际开发中的应用封装通过访问修饰符(public/private/protected等)控制成员的可见性public class BankAccount { private decimal _balance; // 封装字段 public decimal Balance _balance; // 通过属性暴露 }继承使用:符号实现类继承子类获得父类非私有成员public class Animal { /*...*/ } public class Dog : Animal { /*...*/ } // Dog继承Animal多态通过虚方法(virtual)和重写(override)实现public class Shape { public virtual void Draw() Console.WriteLine(绘制形状); } public class Circle : Shape { public override void Draw() Console.WriteLine(绘制圆形); }3.2 接口与抽象类的区别接口(interface)和抽象类(abstract class)都是实现多态的机制但有以下关键区别特性接口抽象类成员实现全部未实现可包含已实现成员多继承支持多继承单继承访问修饰符默认为public可自定义字段不能包含实例字段可以构造函数不能有可以有版本兼容性添加成员会破坏现有实现添加非抽象方法安全实际选择原则需要多继承或仅定义契约时用接口需要共享代码或部分实现时用抽象类。4. 异常处理与资源管理4.1 try-catch-finally的执行流程完整的异常处理结构包含三个部分try { // 可能抛出异常的代码 File.Open(nonexistent.txt, FileMode.Open); } catch (FileNotFoundException ex) { // 特定异常处理 Console.WriteLine($文件未找到: {ex.FileName}); } catch (Exception ex) { // 通用异常处理 Console.WriteLine($发生错误: {ex.Message}); } finally { // 无论是否异常都会执行 Console.WriteLine(清理资源...); }执行顺序执行try块代码若抛出异常按catch块顺序匹配处理无论是否异常最后执行finally块注意catch块应从具体到抽象排列否则后面的catch将永远不会执行。4.2 using语句的工作原理using语句是处理实现了IDisposable接口对象的语法糖确保资源被正确释放using (var stream new FileStream(file.txt, FileMode.Open)) { // 使用stream对象 } // 自动调用stream.Dispose()编译器会将using语句转换为try-finally块等价于var stream new FileStream(file.txt, FileMode.Open); try { // 使用stream对象 } finally { if (stream ! null) ((IDisposable)stream).Dispose(); }对于需要管理非托管资源(如文件句柄、数据库连接)的类应实现IDisposable接口。5. 集合与LINQ5.1 常见集合类型比较C#提供了丰富的集合类型各有适用场景集合类型描述特点典型用途Array固定大小的数组内存连续访问快已知大小的数据存储List动态数组自动扩容随机访问快最常用的通用集合DictionaryK,V哈希表实现的键值对快速查找键唯一快速键值查找HashSet无序的唯一值集合基于哈希快速包含检查去重操作Queue先进先出(FIFO)队列顺序处理任务队列Stack后进先出(LIFO)栈反向顺序处理撤销操作、表达式求值选择集合时应考虑元素是否唯一、是否需要排序、主要操作(插入/删除/查找)的频率等。5.2 LINQ基础查询操作语言集成查询(LINQ)提供了一种统一的方式来查询各种数据源var numbers new Listint { 1, 2, 3, 4, 5 }; // 查询语法 var evenNumbers from n in numbers where n % 2 0 select n; // 方法语法 var evenNumbers numbers.Where(n n % 2 0);常用LINQ操作符筛选Where - 基于条件过滤元素投影Select - 转换元素形式排序OrderBy/ThenBy - 升序排序分组GroupBy - 按键分组元素聚合Count/Sum/Average - 计算聚合值集合操作Distinct/Union/Intersect - 集合运算LINQ查询采用延迟执行(deferred execution)只有在实际迭代结果时才会执行查询。要强制立即执行可调用ToList()或ToArray()。6. 多线程基础6.1 Thread与Task的区别C#中实现多线程的两种主要方式Thread类操作系统线程的包装创建成本高(约1MB栈内存)直接控制线程生命周期无内置的任务协调机制var thread new Thread(() { Console.WriteLine(线程工作); }); thread.Start();Task类基于线程池的任务抽象轻量级高效利用线程支持async/await异步模式内置取消、延续等机制var task Task.Run(() { Console.WriteLine(任务工作); }); await task; // 使用await等待任务完成现代开发中推荐使用Task除非需要精细控制线程特性。Task能更好地利用线程池资源避免频繁创建销毁线程的开销。6.2 lock关键字与线程安全多线程环境下共享资源的访问需要同步lock是最简单的同步机制private readonly object _lockObj new object(); private int _counter; public void Increment() { lock (_lockObj) { _counter; // 临界区代码 } }关键要点lock的对象应该是私有的引用类型字段避免锁定this或Type等公共对象临界区代码应尽可能短可考虑使用Monitor.TryEnter实现超时控制对于更复杂的场景.NET还提供了Mutex跨进程同步Semaphore限制并发数ReaderWriterLockSlim读写分离锁Concurrent集合线程安全集合类7. 字符串处理7.1 字符串不可变性C#中的字符串(string)是不可变的(immutable)这意味着字符串对象一旦创建就不能修改所有看似修改的操作(如Replace、Substring)都返回新字符串频繁修改会导致大量临时对象影响性能string s1 hello; string s2 s1.Replace(h, H); // 创建新字符串Hello Console.WriteLine(s1); // 输出hello(原字符串未变)对于需要频繁修改字符串的场景应使用StringBuildervar sb new StringBuilder(); for (int i 0; i 100; i) { sb.Append(i.ToString()); } string result sb.ToString(); // 只在最后生成字符串7.2 字符串比较最佳实践字符串比较需要考虑文化区域和大小写敏感性string a hello; string b HELLO; // 不推荐 - 依赖当前区域设置 bool equal1 a.Equals(b, StringComparison.CurrentCultureIgnoreCase); // 推荐 - 明确指定比较方式 bool equal2 string.Equals(a, b, StringComparison.OrdinalIgnoreCase);常用比较方式Ordinal基于Unicode码点最快OrdinalIgnoreCase忽略大小写的码点比较CurrentCulture考虑当前区域排序规则InvariantCulture固定文化区域比较在内部标识符、文件路径等场景应使用Ordinal比较面向用户的字符串使用Culture相关比较。8. 委托与事件8.1 委托的定义与使用委托(delegate)是类型安全的函数指针用于实现回调机制// 定义委托类型 public delegate void LogMessage(string message); // 使用方法匹配委托签名 public void ConsoleLogger(string msg) Console.WriteLine(msg); // 创建委托实例 LogMessage logger ConsoleLogger; logger(测试消息); // 调用委托现代C#通常使用内置的Action和Func泛型委托避免自定义委托类型Actionstring logger ConsoleLogger; // 无返回值 Funcint, int, int adder (a, b) a b; // 有返回值8.2 事件的实现原理事件(event)是基于委托的发布-订阅模式实现public class Publisher { // 定义事件 public event EventHandlerEventArgs SomethingHappened; protected virtual void OnSomethingHappened() { SomethingHappened?.Invoke(this, EventArgs.Empty); } } public class Subscriber { public void Subscribe(Publisher pub) { pub.SomethingHappened HandleEvent; } private void HandleEvent(object sender, EventArgs e) { Console.WriteLine(事件处理); } }关键点事件使用event关键字声明遵循EventHandlerTEventArgs命名模式使用?.操作符安全触发事件事件只能从声明类中触发9. 反射与特性9.1 反射的基本用法反射(reflection)允许在运行时检查类型信息Type type typeof(string); // 获取类型对象 // 获取成员信息 var methods type.GetMethods(); var properties type.GetProperties(); // 动态创建实例 object instance Activator.CreateInstance(type); // 动态调用方法 MethodInfo method type.GetMethod(Substring, new[] { typeof(int) }); object result method.Invoke(instance, new object[] { 2 });反射虽然强大但性能开销较大应避免在性能关键路径上使用。常见用途插件系统序列化/反序列化DI容器ORM框架9.2 特性的定义与应用特性(attribute)是为代码元素添加元数据的机制[Serializable] // 内置特性 public class Person { [Obsolete(改用FullName属性)] // 标记过时 public string Name { get; set; } [JsonProperty(full_name)] // 第三方库特性 public string FullName { get; set; } } // 自定义特性 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class AuthorAttribute : Attribute { public string Name { get; } public AuthorAttribute(string name) Name name; }特性本身不改变程序行为但可以通过反射读取并影响程序逻辑。常用内置特性[Serializable]标记可序列化[Obsolete]标记过时成员[DllImport]声明外部方法[Conditional]条件编译10. 异步编程基础10.1 async/await模式C#的异步编程模型基于async和await关键字public async Taskstring FetchDataAsync() { using var client new HttpClient(); // 异步等待网络请求 string result await client.GetStringAsync(https://example.com); return result.ToUpper(); } // 调用异步方法 var data await FetchDataAsync();关键概念async标记方法包含异步操作await暂停方法执行直到任务完成异步方法通常返回Task或TaskT避免async void除非是事件处理程序10.2 避免常见异步陷阱异步编程中容易犯的错误死锁在UI线程同步等待异步方法// 错误做法 - 可能导致死锁 var result FetchDataAsync().Result; // 正确做法 var result await FetchDataAsync();忽略异常未处理异步方法抛出的异常// 错误做法 - 异常被忽略 _ SomeAsyncMethod(); // 正确做法 - 处理异常 try { await SomeAsyncMethod(); } catch (Exception ex) { // 处理异常 }过度并行无限制创建并行任务// 错误做法 - 可能耗尽资源 var tasks urls.Select(url DownloadAsync(url)); await Task.WhenAll(tasks); // 改进方案 - 限制并发数 await Task.WhenAll(urls.Select(url DownloadAsync(url)) .AsParallel() .WithDegreeOfParallelism(10));异步编程是现代C#开发的核心技能合理使用可以显著提升应用响应能力和资源利用率。