摘要
本文系统梳理了.NET开发人员在面试中常见的核心知识点,涵盖从初级到中级的关键概念。内容分为初级.NET开发人员和中級.NET开发人员两大部分,详细解析了进程与线程、Windows服务、内存管理、类型系统、反射、垃圾回收等20个重要主题,旨在帮助开发者构建完整的.NET知识体系,提升面试准备效率。
初级.NET开发人员 - 任何使用.NET的人都应知道的核心概念
1. 进程与线程:概念、区别与联系
进程(Process)是操作系统进行资源分配和调度的基本单位,拥有独立的虚拟地址空间、代码、数据和系统资源。每个进程至少包含一个线程,进程之间相互隔离,不能直接共享内存空间。
线程(Thread)是CPU调度的基本单位,是进程内的执行单元。同一进程内的多个线程共享进程的代码段、数据段和系统资源,但拥有独立的栈空间和程序计数器。
核心区别:
- 资源分配:进程是资源分配的基本单位,线程是CPU调度的基本单位
- 内存空间:进程间内存相互隔离,线程间共享进程内存空间
- 通信方式:进程间通信需要IPC机制,线程间可直接通过共享内存通信
- 创建开销:创建进程开销大,创建线程开销小
- 稳定性:一个进程崩溃不会影响其他进程,一个线程崩溃可能导致整个进程崩溃
多线程的优势:在多核CPU环境下,多线程可以充分利用CPU资源,提高程序响应速度和吞吐量。但需要注意线程同步、死锁、竞态条件等问题。
2. Windows服务:特性、生命周期与管理
Windows服务是在Windows操作系统后台运行的长时期运行程序,通常没有用户界面,以系统账户(默认System)或指定用户身份运行。
与标准EXE程序的主要区别:
| 特性 | Windows服务 | 标准EXE程序 |
|---|---|---|
| 用户界面 | 无标准UI,通过服务控制管理器管理 | 通常有图形界面或控制台界面 |
| 生命周期 | 随系统启动/停止,或手动控制 | 用户启动/关闭 |
| 运行账户 | System账户或指定服务账户 | 当前登录用户账户 |
| 启动类型 | 自动、手动、禁用、延迟启动 | 用户手动执行 |
| 交互性 | 通常不与桌面交互 | 直接与用户交互 |
服务启动类型:
- 自动:系统启动时自动启动服务
- 手动:需要用户或应用程序手动启动
- 禁用:服务被禁用,无法启动
- 延迟启动:系统启动后延迟启动,避免影响启动速度
3. Windows进程内存限制与系统设计影响
在32位Windows系统上,单个进程可访问的最大内存地址空间为2GB(用户模式),另外2GB保留给内核模式使用。这是由32位系统的地址空间限制决定的:2³² = 4GB,其中用户空间和内核空间各占2GB。
计算公式:单个进程最大内存 = 2^(处理器位数) / 2
- 32位系统:2³² / 2 = 2GB
- 64位系统:理论上可达16EB,实际受操作系统版本和物理内存限制
与虚拟内存的关系:单个进程能访问的最大内存量通常是系统最大虚拟内存的一半,因为另一半需要分配给操作系统内核使用。
对系统设计的影响:
- 大内存应用:需要处理大量数据的应用程序(如数据库、科学计算)应考虑使用64位系统
- 内存管理:需要精细管理内存使用,避免内存泄漏
- 进程拆分:对于需要大量内存的应用,可考虑拆分为多个进程
- 虚拟内存配置:合理设置页面文件大小,避免频繁的页面交换
4. EXE与DLL:执行文件与动态链接库
EXE(可执行文件):
- 包含程序入口点(如Main方法)
- 可直接由操作系统加载执行
- 拥有独立的进程空间
- 通常包含完整的应用程序逻辑
DLL(动态链接库):
- 不包含程序入口点,不能直接执行
- 包含可被其他程序调用的函数、类、资源
- 在运行时被加载到调用进程的地址空间
- 支持代码重用和模块化开发
.NET中的程序集:在.NET中,EXE和DLL都是程序集(Assembly),都包含IL代码、元数据和清单。区别在于EXE程序集包含入口点,而DLL程序集不包含。
5. 强类型与弱类型:安全性与灵活性的权衡
强类型语言:
- 定义:在编译时确定变量类型,类型检查在编译阶段完成
- 特点:类型安全、性能高、编译时错误检测
- 示例:C#、Java、C++、TypeScript
- 优势:
- 编译时类型检查,减少运行时错误
- 更好的IDE支持(智能提示、重构)
- 性能优化(编译器可进行类型相关的优化)
弱类型语言:
- 定义:在运行时确定变量类型,类型检查在运行阶段完成
- 特点:灵活、动态、开发快速
- 示例:JavaScript、Python、PHP、Ruby
- 优势:
- 开发快速,无需声明类型
- 灵活的代码结构,易于原型开发
- 动态特性支持(如eval、动态属性)
选择建议:没有绝对的"更好",应根据项目需求选择。大型企业应用、系统软件适合强类型语言;快速原型、脚本任务适合弱类型语言。C#作为强类型语言,在.NET 4.0后引入了dynamic关键字,提供了有限的动态类型支持。
6. PID:进程标识与故障排除
PID(Process Identifier)是操作系统为每个进程分配的唯一数字标识符,用于在系统内唯一标识一个进程。
在故障排除中的应用:
- 进程监控:使用任务管理器、Process Explorer等工具查看进程状态
- 资源分析:通过PID关联进程的CPU、内存、IO使用情况
- 调试附加:在Visual Studio中通过PID将调试器附加到目标进程
- 性能分析:使用性能计数器按PID跟踪进程性能指标
- 进程终止:通过taskkill命令或API终止问题进程
常用命令:
# 查看所有进程及PID tasklist 按PID终止进程 taskkill /PID 1234 /F 查看特定进程的详细信息 wmic process where processid=1234 get *7. TCP/IP端口监听限制
在TCP/IP协议栈中,单个端口在同一时刻只能被一个进程监听。这是由套接字(Socket)的绑定机制决定的:
- 独占绑定:当进程绑定到某个端口后,该端口就被该进程独占
- 绑定冲突:其他进程尝试绑定同一端口时会收到"Address already in use"错误
- 例外情况:SO_REUSEADDR选项允许同一端口被多个套接字绑定,但通常用于特定场景(如快速重启服务)
实际应用中的解决方案:
- 端口复用:使用反向代理(如Nginx、IIS ARR)将请求分发到多个后端进程
- 进程池:主进程监听端口,通过进程池处理连接(如IIS的w3wp.exe)
- 不同协议:TCP和UDP可以同时使用同一端口号
8. GAC:全局程序集缓存
GAC(Global Assembly Cache)是.NET Framework中的全局程序集缓存,位于%windir%\assembly目录下。
解决的问题:
- 程序集共享:多个应用程序可以共享同一个程序集,避免重复部署
- 版本控制:支持并行部署同一程序集的不同版本
- DLL Hell:解决传统Windows中DLL版本冲突问题
- 安全性:强名称程序集提供身份验证和完整性保护
GAC中的程序集要求:
- 必须具有强名称(Strong Name)
- 包含公钥/私钥对签名
- 包含版本号、文化信息和公钥标记
程序集加载顺序:
- 检查GAC(如果程序集有强名称)
- 应用程序基目录(bin文件夹)
- 私有路径(如probing指定的路径)
- 代码基位置(如CodeBase指定的URL)
管理命令:
# 安装程序集到GAC gacutil /i MyAssembly.dll 从GAC卸载程序集 gacutil /u MyAssembly 查看GAC中的程序集 gacutil /l中级.NET开发人员 - 深入理解.NET平台
1. 编程范式:面向接口、面向对象与面向方面
面向对象编程(OOP):
- 核心概念:封装、继承、多态
- 设计原则:SOLID原则(单一职责、开闭原则、里氏替换、接口隔离、依赖倒置)
- 优势:代码复用、模块化、易于维护
- 局限:横切关注点(如日志、安全、事务)难以模块化
面向接口编程(IOP):
- 核心思想:"针对接口编程,而不是针对实现编程"
- 实现方式:定义接口契约,由具体类实现
- 优势:松耦合、易于测试、支持依赖注入
- 应用:插件架构、服务抽象、测试替身
面向方面编程(AOP):
- 核心概念:将横切关注点(cross-cutting concerns)从业务逻辑中分离
- 实现机制:通过代理、拦截器、编译时织入等方式
- 典型应用:日志记录、性能监控、事务管理、安全检查
- .NET实现:PostSharp、Castle DynamicProxy、ASP.NET Core中间件
三者关系:面向接口是面向对象的重要原则,面向方面是对面向对象的补充,专门处理横切关注点。
2. 接口与抽象类:契约与实现的平衡
| 特性 | 接口(Interface) | 抽象类(Abstract Class) |
|---|---|---|
| 定义 | 行为契约,只有方法签名 | 部分实现的类,可包含具体实现 |
| 实现 | 不能包含实现(C# 8.0前) | 可包含具体方法和抽象方法 |
| 继承 | 一个类可实现多个接口 | 一个类只能继承一个抽象类 |
| 构造函数 | 不能有构造函数 | 可以有构造函数 |
| 字段 | 不能包含字段(C# 8.0前) | 可以包含字段 |
| 访问修饰符 | 默认public,不能使用其他修饰符 | 可以使用各种访问修饰符 |
| 版本兼容 | 添加新成员会破坏现有实现 | 添加具体方法不会破坏现有派生类 |
选择原则:
- 需要多重继承时选择接口
- 需要为派生类提供公共实现时选择抽象类
- 定义行为契约时选择接口
- 定义类型层次结构时选择抽象类
3. 反射:运行时类型探索与操作
反射(Reflection)是.NET框架提供的在运行时检查、创建和操作类型、对象、程序集和模块的能力。
反射的核心类:
- System.Type:表示类型声明
- System.Reflection.Assembly:表示程序集
- System.Reflection.MethodInfo:表示方法
- System.Reflection.PropertyInfo:表示属性
- System.Reflection.FieldInfo:表示字段
反射的主要用途:
- 类型发现:在运行时获取类型信息
- 动态创建:动态创建类型实例
- 晚期绑定:在运行时调用方法和访问属性
- 特性处理:读取和操作自定义特性
- 代码生成:动态生成IL代码
示例代码:
using System; using System.Reflection; public class ReflectionExample { public static void Main() { // 获取类型信息 Type type = typeof(string); Console.WriteLine($"类型名称: {type.Name}"); Console.WriteLine($"完全限定名: {type.FullName}"); Console.WriteLine($"程序集: {type.Assembly.FullName}"); // 获取所有公共方法 MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance); Console.WriteLine($"\n公共方法数量: {methods.Length}"); // 动态创建实例 object instance = Activator.CreateInstance(typeof(StringBuilder)); // 动态调用方法 MethodInfo appendMethod = instance.GetType().GetMethod("Append", new Type[] { typeof(string) }); object result = appendMethod.Invoke(instance, new object[] { "Hello, Reflection!" }); Console.WriteLine($"动态调用结果: {result}"); } }性能考虑:反射操作比直接