资讯动态

C#复习核心路线:从语法基础到通讯视觉与Web后端实战

发布时间:2026/9/8 8:00:46 来源:尧图企业网站定制
说实话看到“C#复习代码”这个标题我第一反应是又一份被遗忘在硬盘角落的笔记。但真把热搜词和讨论串拉平了看这东西远比“复习”两个字丰富。它背后不是单纯的语法回顾而是C#开发者在实际工作中反复遇到的三大战场基础语法的死角、以socket/串口/扫码枪为代表的设备通讯、以及以Halcon/VisionMaster和Web后端为代表的多场景整合。很多人面试前临时抱佛脚刷题或者项目做到一半发现委托、异步、字节序这些基础理解不扎实其实缺的就是一份能串起“语法—通讯—视觉—后端”的复习主线。这篇内容我按自己这几年折腾上位机、对接视觉SDK、写Web服务的经验把C#复习拆成了几条可执行的路线覆盖语法高频点、异步并发、串口与TCP通讯、机器视觉整合、Web框架实践外加一份面试避坑清单。适合三类人看准备C#面试的求职者、刚转行做上位机或工业软件的开发以及项目做了一半想系统补基础的半路出家人。1. 先把复习路线定下来C#复习到底该捡起哪些东西1.1 为什么C#面试和实操总在“基础语法”上翻车很多人的C#学习路径是从视频教程开始的跟着敲几个控制台程序就觉得自己会了。但一碰到真实项目问题全出来了扫码枪返回的一串字符怎么稳定截取串口DataReceived事件里能不能直接操作UISocket接收缓冲区粘包了怎么处理海康相机SDK回调里能不能执行耗时任务。这些问题的根子全都落在几个最基础的语言机制上——类型转换、委托事件、线程同步。这些年我面试过不少候选人也帮朋友做过技术面试。发现一个规律只要把问题从“你知道ref和out的区别吗”换成“你写个方法把int数组里每个元素的字节序反转一下再转成十六进制字符串”很多人就开始发懵。这不是技术深度不够而是复习时只背结论不追原理。所以这篇复习文章不打算列一堆知识点而是按实际工作中“会被卡住”的场景来组织每个点尽量给一段可以直接跑的代码让大家在动手里把基础夯结实。1.2 两类典型读者跳槽面试型和项目复盘型第一类是准备跳槽的面试型。这类人时间紧目标明确复习重点应该放在高频面试题上比如string和StringBuilder的底层区别、async/await的状态机原理、lock和Monitor和SpinLock各自的适用场景、委托和事件的区别、结构体和类的内存分配差异。我会在文章里用专门的章节把这些题串起来讲。第二类是项目复盘型多见于做上位机、机器视觉集成的工程师。这类人收到一个需求——比如“用C#写个能和海康VisionMaster通讯的小工具”或者“扫码枪触发后自动读数据并写入数据库”——平时忙于业务没时间系统看书需要的是快速找回手感。我会重点写串口、TCP/UDP、视觉SDK调用这块补充协议选型和通讯框架设计思路。还有一类读者是刚入门不久的学生或转行者他们的特点是代码敲得少但搜索能力强。对这部分人我建议先照着文章里的代码把环境跑通再回头理解概念。复习本身不是目的能在代码里解决问题才是。2. 高频语法点逐一击破类型、字符串、委托与结构体2.1 类型系统byte、char与string转换的那些坑C#面试里有个高频组合拳“c# c byte char”和“c#语言怎样截取字符串”。看起来是弱智问题实际上里面水很深。先说byte和char。byte是无符号8位整数取值范围0到255在串口通讯、Socket收发、文件读写里到处可见。char是Unicode字符底层是16位这正是为什么会有人踩“中文乱码”的坑——你把一个汉字转成byte[]按ASCII解码自然出来一堆问号。正确的转换方式是明确编码string text 上位机; byte[] utf8Bytes Encoding.UTF8.GetBytes(text); // 按UTF-8编码 byte[] asciiBytes Encoding.ASCII.GetBytes(text); // 中文会变成 ? // 反过来从字节数组还原字符串 string restored Encoding.UTF8.GetString(utf8Bytes);这里有个我踩过很多次的坑和PLC通讯时很多设备默认用ASCII码解析而设备侧的中文参数往往用GB2312或GBK。你从Socket缓冲区里拿到的字节不能想当然用UTF-8解码必须先搞清楚设备手册里定义的是哪种编码。复习时可以把Encoding类的常用属性过一遍ASCII、UTF8、UnicodeUTF-16、UTF32再加一个Encoding.GetEncoding(GBK)备用。再说string截取。很多人一提截取就Substring但在工业通讯里一帧数据往往是“固定帧头长度数据体校验”的结构单纯用Substring硬抠容易出问题。比如扫码枪返回的字符串可能是“STX,S/N:123456,STATUS:OK,ETX”更稳妥的做法是用Split按分隔符拆string raw STX,S/N:123456,STATUS:OK,ETX; string[] parts raw.Split(,); string sn null; foreach (string part in parts) { if (part.StartsWith(S/N:)) { sn part.Substring(4); // 去掉 S/N: break; } }如果数据是固定宽度比如每帧120字节第4到第14字节是产品编号直接用Span切片性能更好。C# 7.2之后的Span 在协议解析里特别香因为它不会产生额外字符串对象这在高频扫码场景里能明显降低GC压力。2.2 方法参数与结构体ref、out、in与readonly struct“ref和out有什么区别”是C#面试题里的常青树。标准答案是ref要求变量在传入前先初始化out则可以在方法内部赋值不需要事先初始化两者都传递变量的引用允许在被调方法内修改调用方的变量。但工作中真正高频用到的场景往往是解析协议的缓冲区填充——用ref避免大结构体复制用out表达“这个方法有多个返回值”。in参数是C# 7.2引入的用于只读传递避免复制大结构体的同时保证调用时不会被修改。这是一个“看起来很高级、用起来很舒服”的特性。比如你定义了一个大的readonly struct作为协议帧头按值传参会有拷贝开销加in之后编译器会传引用public readonly struct FrameHeader { public byte Sync { get; } public ushort Length { get; } public byte Cmd { get; } } public void ParseFrame(in FrameHeader header) { // 只能读不能改 }结构体本身也是一个容易翻车的地方。C#里struct是值类型class是引用类型。值类型在栈上分配超出作用域自动回收不吃GC引用类型在堆上分配由GC管理。这带来一个经典面试题“结构体和类在List 里的性能差异”。当List 存class时存的是引用修改元素要list[i].XXX赋值会互相影响存struct时每次索引访问都是值拷贝如果结构体很大反而更慢。所以并不是“值类型一定比引用类型快”要看具体场景。2.3 委托与事件从回调到事件驱动的自然延伸C#的委托是连接“语法”和“架构”的桥梁。串口收到数据、Socket连接断开、相机采图完成这些东西本质上都是“某个事件发生了请你执行一段预先注册的代码”这就是委托的用武之地。基础层面要能分清delegate、Action、Func和event。delegate是自定义委托类型Action是无返回值委托Func是带返回值的委托。event是在委托基础上加了封装只允许外部用和-订阅不能像字段一样随便赋值这避免了外部代码把已有的订阅覆盖掉。实际项目里有一个高频写法用委托事件做“扫码枪触发”模型。扫码枪通常模拟键盘输入串口模式下触发DataReceived事件网口模式下触发数据到达回调。你可以在初始化时订阅事件在事件处理方法里解析数据public class BarcodeScanner { public event Actionstring OnBarcodeScanned; private SerialPort _port; public void Open() { _port.DataReceived DataReceivedHandler; } private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e) { string line _port.ReadExisting(); // 这里是工作线程不要直接操作UI OnBarcodeScanned?.Invoke(line.Trim()); } }这个模式背后的思想是“发布订阅”。上位机软件里扫码数据可能会被多个模块同时使用更新界面、写入数据库、触发相机拍照。如果直接用方法调用来写耦合度会非常高。而用事件广播UI模块只管刷新显示数据库模块只管异步写入互不干涉。复习委托时还要掌握一个进阶点多播委托。用加多个方法调用时按注册顺序依次执行。但有个坑如果其中一个方法抛异常后面的方法不会继续执行。所以事件处理器里一定要自己try/catch别让一个模块的异常拖垮整条链。3. 异步、多线程与并发控制现代C#的核心分水岭3.1 async/await理解状态机而不是背套路我在很多项目里看到这种代码用async void做按钮事件或者Task.Run里套async方法然后.Result阻塞等待。这类写法调试时最难查因为死锁经常是“时好时坏”——尤其是涉及UI线程或ASP.NET的SynchronizationContext时。async/await的底层是一台状态机。编译器把你的异步方法转换成一个状态机结构遇到await时先检查任务是否完成没完成就挂起当前方法并返回一个Task等任务完成后再通过回调继续执行。这中间最容易被忽略的是上下文捕获。在UI线程里await之后默认会尝试回到UI线程继续执行在控制台程序或纯后端服务里没有这个上下文行为就略有不同。实际写上位机软件时我强烈建议所有事件处理器不要用async void除非是UI事件比如Button_Click。因为async void的异常无法被外部捕获一旦处理逻辑抛错整个进程可能崩掉。更稳妥的方式是用async Task然后包一层try/catchprivate async void OnConnectButtonClick(object sender, EventArgs e) { try { await ConnectAsync(); // 内部实现用Task.Run或真正的异步API } catch (Exception ex) { MessageBox.Show(ex.Message); } }3.2 锁、SpinLock与并发集合的适用场景并发控制是“C#面试题”里另一大热门。“lock”关键字几乎是标准答案但很多人不知道lock其实是Monitor的语法糖。lock(this)这种写法是有坑的——如果this作为私有锁被其他代码意外锁定会有死锁风险。规范的写法是准备一个object类型的专用锁字段。SpinLock适合“锁的粒度很小、锁等待时间极短、但执行频率极高”的场景。比如一个启动频率很高的后台服务需要原子地更新某个计数器用lock可能因为上下文切换带来较大开销SpinLock会让线程自旋等待几百纳秒性能更好。但这个等待本身是消耗CPU的如果临界区内有耗时操作千万别用SpinLock自旋会拖垮整个CPU。并发集合要记住几个ConcurrentDictionary用于多线程读写字典ConcurrentQueue是线程安全的队列BlockingCollection可以在生产者消费者模式里做阻塞取数据。上位机软件里最经典的生产者消费者场景是扫码数据采集扫码线程往队列里写入库线程从队列里取中间用BlockingCollection连接天然解耦。3.3 用属性事件检测变量变化热词里有一条“c# 怎么检测变量 数值变化”。这问题放到WPF里就是INotifyPropertyChanged放到工业场景里就是“某个设备状态量变了需要主动通知UI”。更通用的做法是把变量封装成属性在set访问器里触发事件public class DeviceStatus : INotifyPropertyChanged { private bool _isRunning; public bool IsRunning { get _isRunning; set { if (_isRunning ! value) { _isRunning value; OnPropertyChanged(nameof(IsRunning)); } } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }这里的关键是加了一个“新旧值是否相同”的判断避免无意义的通知。在WPF里这个模式天然支持数据绑定界面自动刷新。在非WPF的控制台程序或上位机框架里这种模式同样有效——订阅PropertyChanged事件再自己响应即可。注意事件触发的线程问题如果属性在后台线程被修改而UI绑定在UI线程WPF会自动封送但手写订阅时就得自己用Invoke或BeginInvoke处理跨线程更新。4. 上位机与网络通讯C#在工业场景里的硬功夫4.1 串口与SerialPort实战扫码枪触发事件模型工业上位机开发中串口是最常见的通讯方式之一。很多设备比如扫码枪、电子秤、简单PLC都通过串口输出数据。C#的SerialPort类封装得比较完善核心参数有波特率、数据位、停止位、校验位常见的组合是9600,8,N,1。SerialPort的正确打开方式不是轮询ReadLine而是订阅DataReceived事件。这个事件在后台线程触发意味着事件处理器里不能直接修改UI控件需要Invoke到UI线程。数据读取也有讲究流式数据不会按你想的“帧”边界到达可能一条消息被拆成两段或者几段拼在一起这就是所谓的“粘包/拆包”。最简单的解决方式是约定一个结尾符号例如换行符或回车换行然后逐行读取private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e) { SerialPort sp (SerialPort)sender; string data sp.ReadExisting(); _buffer.Append(data); while (true) { string line ExtractLineFromBuffer(); // 自己实现/省略 if (line null) break; ProcessLine(line); } }如果你的通讯协议是固定长度帧则等待缓冲区长度达到帧长再处理如果是不定长则要有帧头和长度字段按长度拆包。复习时可以把“帧格式设计”作为一个专题因为后面Socket通讯、FINS UDP等也离不开这套思路。4.2 Socket/TCP连接数、异步收发与心跳机制“c# tcp连接数量多少”这个热搜说明很多人对TCP高并发有误会。单台服务器能建立的TCP连接数不是一个固定值受文件描述符、内存、端口范围限制普通服务器几万个连接并不夸张。但上位机场景通常不是高并发服务器而是单客户端长连接或者少量客户端并发。对于单客户端长连接写清楚接受、断线重连、心跳就够了。C# Socket编程有同步、异步回调和async/await等写法。老式BeginReceive/EndReceive容易写出嵌套地狱直接用异步包装好的Socket.ReceiveAsync(Action)或用NetworkStream配合ReadAsync更直观。推荐优先用.Net的TcpListener/TcpClient封装它们内部已经把Socket细节藏起来了适合大多数上位机工具。心跳也很重要。设备长时间不通信TCP连接可能已被中间设备静默断开程序却不知道。常规做法是每N秒发送一次心跳报文超过M秒没收到响应或对方的保活回复就判定连接失效触发重连。我在写一个“工业级网口通讯助手”时给心跳模块加了独立Timer还把断线重连做成了指数退避避免设备故障恢复后瞬间大量请求压垮对方。4.3 上位机通讯协议选型串口、TCP、UDP还是Modbus很多初学者不知道什么时候用TCP什么时候用UDP什么时候直接走Modbus。这决定了通讯架构的合理性。简单来说如果需要可靠传输、数据不能丢选TCP如果对实时性要求高、允许少量丢包、且数据帧很短比如三菱PLC的FINS UDP则UDP更合适如果是对接标准的工业设备如Modbus仪表、变频器直接按Modbus协议封装最省事。“c# fins udp 怎样建立连接”这条热搜很有代表性。FINS是欧姆龙PLC的通讯协议UDP方式不使用真正的“连接”而是向指定IP和端口发送UDP报文然后在接收端口等待响应。这要求发送端设计好“事务ID”来匹配请求与响应因为UDP是不可靠的超时后要重发。还有一点FINS UDP命令里会区分网络号、节点号、单元号复习时最好把地址映射关系搞清楚否则调试日志里全是“节点不存在”其实是你把目标地址算错了。4.4 用C#写扫码枪触发与后台线程的正确姿势扫码枪在产线中应用极多触发模型分两种手动按键触发和自动连续扫码。上位机软件要做的核心工作就是监听端口数据解析出条码字符串然后触发后续业务比如调用视觉系统拍照。这里的核心技巧是数据校验扫码枪有时会多发出一个回车有时因为条码质量差会漏读一个字符最好的方式是在协议层约定好条码的起始和结束标志或者至少做基本的长度、字符集校验。我的经验是所有来自设备的数据都当成“不可靠输入”处理先校验再入库。有一次客户反馈扫码偶发乱码排查后发现是线缆过长导致串口电平衰减换了一根屏蔽线解决。这个案例说明上位机开发调试时通讯链路自身的物理问题往往被忽略。复习时可以刻意储备一些硬件排查经验比如用串口调试助手先验证链路是否正常再甩锅给代码。5. C#与机器视觉Halcon、VisionPro、ONNX与相机SDK5.1 为什么视觉厂商都留C#接口机器视觉领域里C#虽然不是图像算法的主力语言但却是“整合层”的首选。Halcon有C#接口VisionPro直接卖.NET控件海康VisionMaster提供了.NET SDK。原因很简单视觉项目最终要嵌入产线软件、MES系统、上位机框架而这些系统大多用C#写界面和业务流程。算法核心可以由C完成但C#负责的是工程化封装、UI交互、数据存储、网络通讯。复习时不用把Halcon或VisionPro的所有算法都记住但一定要掌握“加载图像—调用算法—显示结果”这条链路的C#写法。比如Halcon的HObject和HImage在C#里对应的类型转换以及注意释放非托管资源。很多新人在这里漏掉Dispose长时间运行后内存爆掉。5.2 VisionMaster与C#通讯协议选型“海康相机软件VisionMaster与C#上位机软件通讯使用什么协议比较好”这个问题我直接给结论分数据量和实时性。如果只是发送任务号、接收结果文本优先用TCP/自定义文本协议或HTTP REST接口开发简单、调试直观如果要求高吞吐的实时图像流或频繁调参数用VisionMaster自带的SDK集成更稳直接用官方提供的.NET DLL如果现场已有PLC且希望视觉结果直接进PLC那就走Profinet/EtherNet/IP这类工业协议由VisionMaster或PLC侧主动拉取。我自己实践中用得最多的是TCP文本协议自定义帧格式C#这边写一个VisionMasterClient类用来发送“开始检测”命令、等待返回“OK/NG坐标数据”。好处是不依赖具体SDK版本只要VisionMaster那边配置好触发输出两边改字符串格式就能联调。缺点是调试时字段对齐比较痛苦所以帧格式文档一定要写清楚。5.3 在C#中集成ONNX素描模型随着AI落地C#里跑ONNX模型的需求越来越多。比如热词里的“c# onnx model 素描模型”这其实是要在C#程序中加载一个PyTorch训练好、导出为ONNX的深度学习模型实现图像风格转换之类的功能。C#侧推荐用Microsoft.ML.OnnxRuntime。核心步骤是三步加载模型、准备输入Tensor、执行推理并解析输出。图像预处理是最容易出错的地方——模型训练时输入是归一化到0~1的float数组还是按通道减均值除方差在C#代码里必须严格对齐。例如一个输入为1x3x512x512的素描模型C#侧要做的是把Bitmap转成byte[]再归一化并重新排成CHW格式using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; var session new InferenceSession(sketch.onnx); float[] inputData PreprocessBitmap(bitmap); // 自己实现缩放归一化CHW var inputTensor new DenseTensorfloat(inputData, new[] { 1, 3, 512, 512 }); var inputs new ListNamedOnnxValue { NamedOnnxValue.CreateFromTensor(input, inputTensor) }; using (var results session.Run(inputs)) { var output results.First().AsTensorfloat(); // 后处理成图片 }这里的教训是输入节点的名字和维度必须以模型为准直接用Netron打开ONNX文件看一眼就清楚了。还有一个容易忽略的性能点如果只是在单张图片上做推理可以直接同步调用但如果是摄像头实时流建议把推理放到单独的Task线程或使用OnnxRuntime的并行推理选项否则会阻塞UI线程。5.4 BackgroundSubtractorMOG2未定义构造函数的坑热词里有“c# backgroundsubtractormog2未定义构造函数”这显然是OpenCvSharp版本或命名空间的坑。旧版本OpenCvSharp里BackgroundSubtractorMOG2的构造函数是new BackgroundSubtractorMOG2()新版改成工厂方法BackgroundSubtractorMOG2.Create()。如果你引用了旧代码习惯就会碰到“未定义构造函数”或“不包含适用的构造方法”。正确的做法是// 新版OpenCvSharp var mog2 BackgroundSubtractorMOG2.Create(history: 500, varThreshold: 16, detectShadows: true); // 处理帧 Mat fgMask new Mat(); mog2.Apply(frame, fgMask);复习时建议先查清版本再看OpenCvSharp的源码或示例官网很多代码是C原版的直接翻译构造函数在C#里不一定同样暴露。多看看Github上的Issue能省很多时间。6. Web后端与现代框架从ASP.NET Core到ABP6.1 用StreamReader读取HTTP请求体的正确姿势热词里有“asp.net c# streamreader(httpcontext.request.body)”这是ASP.NET Core里很常见的需求在中间件或Action里读取请求体。但Request.Body是个只能读一次的流如果你先在过滤器里读了Controller里再读就是空。正确姿势是用EnableBuffering让流可以回退public void Configure(IApplicationBuilder app) { app.Use(async (context, next) { context.Request.EnableBuffering(); using (var reader new StreamReader(context.Request.Body, Encoding.UTF8, leaveOpen: true)) { string body await reader.ReadToEndAsync(); // 处理body context.Request.Body.Position 0; // 关键重置位置 } await next(); }); }核心是leaveOpen: true和重置Position否则后续读取就是空的。在调试WebAPI时这个“读不到请求体”的问题很常见我大概有一半的接口排查时间花在“body被谁先读走了”上。6.2 Swagger加账号密码访问“c# mvc swagger ui增账号密码访问”是典型的内部工具需求。团队里Swagger一上线外网也能访问接口文档裸奔甚至可以被调用这时就得给Swagger加个认证。最简单的方案是用Basic Auth加个中间件用户名密码正确才允许访问Swagger路径。public class SwaggerAuthorizationMiddleware { private readonly RequestDelegate _next; private readonly string _username admin; private readonly string _password 123456; public async Task InvokeAsync(HttpContext context) { if (context.Request.Path.StartsWithSegments(/swagger)) { string authHeader context.Request.Headers[Authorization]; if (authHeader ! null authHeader.StartsWith(Basic )) { string encoded authHeader.Substring(Basic .Length).Trim(); string decoded Encoding.UTF8.GetString(Convert.FromBase64String(encoded)); string[] parts decoded.Split(:, 2); if (parts.Length 2 parts[0] _username parts[1] _password) { await _next(context); return; } } context.Response.StatusCode StatusCodes.Status401Unauthorized; context.Response.Headers[WWW-Authenticate] Basic; return; } await _next(context); } }这个写法适合内部小工具生产环境还是建议换成更正式的认证方式但作为复习和工具开发完全够用。6.3 简单OA系统和Excel后台处理“简单oa系统 c#”和“c# 后台处理前端传过来的excel”这两条放一起说。做一个简单OA系统基础配置离不开ASP.NET Core MVC或Blazor、EF Core操作数据库、身份认证这几个模块。而“后台处理Excel”的常见场景是用户在页面导入Excel后端读取并写入数据库。后端处理时文件可能很大几万行数据如果同步处理会让请求超时所以要丢到后台任务里执行。处理Excel我常用NPOI或ClosedXML。NPOI更成熟支持.xls和.xlsxClosedXML的API更现代写起来舒服但只支持.xlsx。核心步骤是打开工作簿、读取第一个Sheet、遍历行、逐行读取单元格值。这里要注意单元格类型日期、数字、文本在NPOI里要判断CellType再取值否则会出现把“2025-01-01”读成一串数字的诡异问题。Excel导入还有一个脏数据问题前端模板里明明限制了列数但用户自己调整格式后列对不上。稳妥做法是读取表头按表头名称定位列索引不要写死第几列。尤其是“上传—解析—校验—入库”这个流程校验环节不能省常见的错误是重复数据、空行、非法格式。后台任务处理时还要记录处理进度和失败行号方便用户下载错误报告。6.4 ABP框架与IdentityServer4企业级开发的复习重点ABP是一个基于ASP.NET Core的完整应用框架自带模块化、多租户、审计日志、权限管理、后台任务等功能。复习ABP时核心要掌握的是它的模块化思想和依赖注入机制。每个模块都继承AbpModule通过DependsOn声明依赖关系。这个设计在大型项目中非常有用因为每个功能模块可以独立开发、独立测试。IdentityServer4是OpenID Connect和OAuth2的认证授权框架。热词“c# 客户端怎么对接 identityserver4”问的就是客户端怎么获取token然后带着token访问受保护的API。常规流程是客户端向认证服务器请求token拿到access_token后在调用API时放到Authorization请求头里。C#里可以用IdentityModel库封装请求过程。复习时要把OAuth2的client credentials、password、authorization code几种模式都过一遍特别是要知道使用场景——后台服务之间用client credentials用户登录用authorization code或password模式。6.5 MySQL访问与EF Core数据库访问在C#里绕不开ADO.NET、Dapper和EF Core。ADO.NET最底层Dapper轻量高效EF Core功能完整。上位机或小型管理系统用Dapper足够复杂Web系统建议EF Core加自动迁移。MySQL在.NET里推荐用Pomelo.EntityFrameworkCore.MySql配置简单注意版本要和MySqlConnector对应。复习时要把“连接字符串里AllowUserVariablesTrue”等常用配置记下来否则一些复杂SQL执行时可能报参数化错误。另外EF Core的延迟加载在生产环境容易引发N1查询复习时要学会用Include或AsSplitQuery来控制加载策略。7. 面试高频题与避坑实录7.1 面试中容易被问懵的基础题整理一下这些年面试中被问过的C#基础题结合项目实际最容易被问懵的几个点第一string vs StringBuilder。背诵版答案是string不可变频繁拼接用StringBuilder。但如果面试官追问“字符串拼接到底发生了什么”你要能答出string每次拼接都会生成新对象旧对象等待GC大量拼接会产生严重的内存碎片。而StringBuilder内部是个字符数组超出容量自动扩容。实际工作中日志拼装、循环拼接这类场景直接用string好理解数据量大再上StringBuilder。第二和Equals。默认情况下比较引用值类型比较值但string是个特例重载了Equals比较内容则可以被子类重写。典型的面试陷阱是object a new string(new char[] { a, b, c }); object b new string(new char[] { a, b, c }); Console.WriteLine(a b); // False引用不同 Console.WriteLine(a.Equals(b)); // True内容相同第三装箱和拆箱。值类型转object或接口就是装箱反向就是拆箱。面试喜欢问“List存int有什么坑”答案就是每个int都装箱大量装箱会产生大量GC垃圾。性能敏感的代码应该用List 或泛型容器。7.2 项目复盘类面试题的答题思路除了基础题很多岗位会问“你做过什么项目遇到过什么困难”。我总结了一个答题框架项目背景、个人职责、技术选型原因、关键问题发现与解决、量化结果。比如你做过一个基于串口的上位机开发项目可以这样组织背景是产线需要自动采集扫码数据并上传MES职责是负责串口通讯、协议解析和数据库写入技术选型上用SerialPort加自制帧协议因为设备固定、数据量小、不需要TCP复杂度关键问题是粘包和跨线程UI更新解决方式是缓冲区累积加帧解析UI用BeginInvoke量化结果是扫码数据入库率100%异常率降到0.1%以内。这样回答既展示了项目能力又体现了思考深度。7.3 我在复习过程中踩过的坑最后分享几个我实际复习和做项目时踩过的坑。第一个是“c# 控制台程序”里的异步问题。很多人以为Console.WriteLine能直接用在异步回调里结果发现程序主线程跑完就退出了异步任务没执行完。解决办法是主线程用Console.ReadKey()阻塞或者使用await Task.Delay之类让主线程等待。复习时最好把这个点写进笔记因为控制台工具太常用了很多人在这里摸不着头脑。第二个是“c# 更新本地时间”这类系统级操作。C#里改本地时间不是简单调API就行涉及到管理员权限、时间同步服务冲突。代码可以写但运行时会遇到权限不足、公司组策略锁时间等一堆环境问题。复习时如果看到类似需求先考虑用外部命令行搞定别硬啃API。第三个是关于线程安全的全局状态。我写过很多时间是“开一个后台线程然后UI线程读取共享变量”的代码结果偶发数据错乱。后来统一改成用lock或Interlocked或者干脆用Channel做线程间通信。这条经验在面试里说出来通常面试官会点头——因为很多人都在这里栽过跟头。个人经验复习的态度比内容更重要C#这门语言真正难的地方不是语法难而是生态广。你永远不可能把DEvExpress的GridControl、ABP的每个模块、Halcon的算子、ONNX Runtime的全部功能都背下来。实际开发中你只需要掌握一套“遇到问题—定位领域—查文档—搭Demo—集成进项目”的方法就能处理绝大多数需求。我个人的复习方式是把笔记按“通讯/视觉/Web/杂项”分目录每个目录里存“典型Demo项目问题清单踩坑记录”。复习时不是从头翻到尾而是遇到一个写一个像打补丁一样。文章里提到的串口、FINS UDP、VisionMaster、ONNX、EF Core、Swagger这些都是这半年复习过程中实际做过的Demo。这份记录比单纯刷一百道面试题有用得多。如果你也正在经历C#的集中复习阶段不妨把文章里的代码片段挑几个按自己熟悉的场景改一改跑一遍感觉会比只看不写强很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价