尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

C#实战100例:从语法基础到上位机、机器视觉与Web开发全攻略

C#实战100例:从语法基础到上位机、机器视觉与Web开发全攻略 简介面向C#初学者的编程实例合集通过100个精选案例系统覆盖变量、流程控制、面向对象、集合与数据结构、LINQ、异步编程、Windows Forms/WPF等核心主题帮助读者从语法基础平滑过渡到实际项目开发。压缩包内含1717个文件以.cs源代码、.exe可运行程序、.jpg/.ico界面资源及项目配置文件为主完整呈现每个实例的源码、编译结果与设计素材包体仅9.65MB便于随时下载学习。已有490人学习使用适合自学编程、准备面试或作为教学辅助材料。每个示例独立成篇既包含可直接运行的完整项目也配有可对照的说明与调试思路通过动手实践可深入理解委托事件、反射、多线程、文件I/O、XML/JSON处理等进阶机制同时涉及单元测试与网络编程的典型应用有助于巩固理论、举一反三。 一直想把手头这套 C# 100 例整理成文字版因为问的人实在太多了。这 100 个例子里既有 byte 和 char 这种最底层的数据类型题也有上位机 Socket 通信、海康 VisionMaster 对接、Halcon 快速匹配这种工业现场题还有 MVC 集成 Vue、Swagger 加账号密码、后台处理 Excel 这类 Web 方向题。基本把我这些年做 C# 开发踩过的、填平的坑都浓缩进去了。这篇文章就挑里面最有代表性的几个方向展开聊适合刚入门 C# 的、准备转上位机/机器视觉的、以及做 Web 项目遇到瓶颈的朋友直接拿去当实战手册用。1. 100例的选题逻辑从语法点到大项目的四层架构一开始定的标题是“c#100例”但“100例”绝不能是 100 个孤立的知识点。我把它拆成了四层递进结构每一层解决一类问题。做这套题的初衷就是让读者学完以后既能在面试里把基础题答得漂亮也能真到工控现场或者 Web 项目里直接上手干活。1.1 第一层语言本身的语法地基第一层覆盖的是语言本身数据类型byte、char、string、运算符、分支循环、方法、类与接口、泛型、集合、LINQ。很多人在这一步容易犯的错误是“看懂了但写不出来”所以我每个例子都刻意安排成“能跑出结果的小程序”而不是干巴巴的语法片段。比如 byte 和 char 互转那道题看起来是基础中的基础实际上一扯到编码、溢出、Unicode 就不是那么简单了。这类题的好处是能快速建立代码手感后面学任何框架都有底。1.2 第二层常用类库与设计模式第二层是“工具箱”字符串处理、反射、单例模式、事件与委托、File/Stream、串口 SerialPort、Socket、Task 异步编程。这一层我会刻意把相关联的知识点放在同一个例子里。比如反射那题不仅是Assembly.LoadFrom加载 DLL还顺手考了特性Attribute怎么通过反射读取以及 “为什么反射慢、怎么缓存”。这样每一题都能带出至少一个高频面试点学习性价比很高。1.3 第三层和第四层框架与行业场景第三层开始进入框架生态MVC、Web API、EF Core、ABP 框架、MySQL、Swagger、Json 解析。第四层就是行业场景了上位机通信、扫码枪接入、循环数据采集、Halcon 机器视觉、VisionMaster 与 C# 通讯、ONNX 模型推理、后台处理 Excel。选题标准很明确——这个题能不能在真实项目里立刻用上。用不上的例子再炫技我也不收进这 100 例里。很多读者问“为什么没有 WPF 动画那一类”就是因为这些方向虽然写着爽但实际项目里一年也碰不到两次先保证高频场景全覆盖。2. 基础高频题拆解字符串、反射、单例三种典型考法这章挑三个被反复问到的例子都是“看起来简单、实际很考功底”的典型。它们的共同点是表面考语法实际考对 CLR 运行机制的理解。2.1 字符串截取从 Substring 到 Span 的进阶路线字符串截取是 C# 里出现频率最高的操作之一但不同写法性能差很多。最基础的写法是Substring两个参数分别是起始索引和长度容易踩的坑是索引从 0 开始、以及“截到字符串末尾”时长度越界。很多初学者会把第二个参数当成结束位置写str.Substring(3, 8)以为是从第 3 位截到第 8 位结果直接抛异常。正确的末尾截取要写成str.Substring(3)只传一个参数。string str Hello, C# World; // 从索引7开始截取到末尾 string sub1 str.Substring(7); // 从索引7开始截取5个字符 string sub2 str.Substring(7, 5); // 如果只是判断是否包含某个字串用 Contains不要 Substring 再比较 bool hasCSharp str.Contains(C#);如果是在循环里大量做字符串切片Substring每次都会分配新字符串内存压力不小。这时候可以用ReadOnlySpanchar配合 Range 运算符避免分配ReadOnlySpanchar span str.AsSpan(); ReadOnlySpanchar slice span[7..]; // 不产生新字符串提示.NET Core 3.0 支持 Range 语法[1..^1]^1表示倒数第一个字符。但要注意Span 只能在栈上临时用不能存到字段里。2.2 反射能做什么、为什么慢、怎么用才对反射这套题我是和“插件化开发”放在一起讲的。核心就三个动作拿到Type、拿到成员信息、动态调用。最常用的场景是从配置的 DLL 路径加载程序集再根据类名创建实例Assembly asm Assembly.LoadFrom(MyPlugin.dll); Type type asm.GetType(MyPlugin.Main); object instance Activator.CreateInstance(type); MethodInfo method type.GetMethod(Run); method.Invoke(instance, new object[] { 参数 });但反射不是免费的。Invoke走的是后期绑定参数要装箱、方法要解析性能比直接调用慢一到两个数量级。我通常的做法是如果这个调用会频繁发生比如循环里每次都要执行那就把MethodInfo转成强类型委托缓存起来这样只反射一次后面都走委托调用。另外反射拿到的成员信息可以配合特性做数据映射我做过一个导入导出功能就是依靠PropertyInfo.GetCustomAttribute去读 Excel 列名映射的不需要手写几十个字段的赋值。2.3 单例模式线程安全的正确打开方式单例几乎是面试必问题但面试考的和项目里用的完全是两码事。最直接的写法是懒汉式但不能用因为多线程下可能创建两个实例。加lock的双重检查锁是经典写法不过代码稍长容易写错。public sealed class ConfigManager { private static readonly LazyConfigManager _instance new LazyConfigManager(() new ConfigManager()); public static ConfigManager Instance _instance.Value; private ConfigManager() { } }实际项目里我推荐LazyT这个写法IsValueCreated可以判断是否已创建而且默认就是线程安全的。有一点很多人忽略单例不只要锁构造函数如果单例对象里有可变状态读写的成员方法也要考虑线程安全。我在上位机项目里用单例管理串口对象时就因为没有锁发送方法导致两个线程同时写串口数据直接乱掉了。3. 工业上位机专题串口、Socket 与扫码枪上位机开发是 C# 的一大主战场。这类项目的典型特征是有硬件、有通信、有 UI还要求长时间稳定运行。100 例里工业方向占了近 20 题足见这个方向在真实需求里的分量。3.1 Socket 通信与 TCP 连接数量的边界C# 做工业级网口通信最常见的方案是TcpClient/TcpListener底层是 Socket。对于“C# TCP 连接数量多少”这个问题答案不是固定的。客户端这边一个 IP 出去最多能建立的连接数取决于可用端口范围Windows 默认动态端口大约是 28000 个左右所以单客户端主动连同一个服务端理论上不会超过这个数服务端这边瓶颈更多在内存、句柄和监听队列设置上。如果做高并发网关要用SocketAsyncEventArgs或直接上 Kestrel 这类异步网络库。TcpListener listener new TcpListener(IPAddress.Any, 9000); listener.Start(100); // backlog 队列长度 while (true) { TcpClient client await listener.AcceptTcpClientAsync(); _ HandleClientAsync(client); // 后台处理不要阻塞 }这里有个重要的注意点连接建立不等于通信正常。我踩过最多的坑是读写超时设置。工业设备经常出现“网线松了但 TCP 连接还在”的情况如果不设ReceiveTimeout/SendTimeout程序会在读数据时无限期卡住。超时时间建议根据设备协议来定一般 1~3 秒比较合理太短容易误判太长故障发现不及时。3.2 扫码枪触发事件的三种接入方式扫码枪接入几乎是每个上位机项目都会遇到的需求。但很多教程只讲一种接入方式实际现场三种都常见USB 模拟键盘型、串口型、网口型。USB 模拟键盘型最简单扫码枪插上就像键盘一样把条码字符“敲”进当前焦点控件。检测方式是在窗口或文本框的KeyDown/KeyPress事件里判断回车然后把累积的字符当作一次完整扫码结果。注意一个坑如果界面上有意个 TextBox 有焦点扫码内容会直接出现在输入框里要在事件里处理后清除。如果系统输入法是中文状态有些字符会被吞我一直用的是处理PreviewTextInput或者在TextChanged里做累积判断比纯KeyDown稳。串口型扫码枪就是发串口数据用SerialPort.DataReceived接收。波特率常见 9600 或 115200注意ReceivedBytesThreshold如果设置不对一帧数据会拆成多次触发需要自己组装缓存、按结尾换行符判断一帧。网口型扫码枪则是设备作为 TCP 客户端连到电脑的监听端口所以上位机其实是被动接收方要给扫码枪配置好服务器 IP 和端口。3.3 循环数据采集与 UI 刷新卡顿的根治方案“C# 循环数据采集和 UI 刷新卡顿”是上位机里问得最多的问题没有之一。根本原因有两个一是采集循环直接在 UI 线程跑界面自然卡死二是采集线程用Invoke高频刷新控件把 UI 线程塞爆了。正确思路是采集和显示解耦。采集线程只负责读数据、存数据UI 的刷新单独用一个定时器比如每 200 毫秒从缓存里取一次最新值去更新界面。缓存的设计可以用ChannelT或ConcurrentQueueT保证线程安全。private readonly Channelfloat _dataChannel Channel.CreateUnboundedfloat(); // 采集线程 while (_isRunning) { float value ReadDevice(); await _dataChannel.Writer.WriteAsync(value); } // UI 刷新用 DispatcherTimer 或 System.Timers.Timer private void RefreshTimer_Tick(object sender, EventArgs e) { while (_dataChannel.Reader.TryRead(out float value)) { txtValue.Text value.ToString(F2); } }如果数据量实在太大、每帧几万个点那就不适合逐点刷新应该把数据聚合之后一次性刷新比如每 200 毫秒只显示最新一组曲线的最大值、最小值、平均值。实测下来这种方法能把 UI 占用率从 90% 降到 5% 左右。4. 机器视觉集成Halcon、VisionMaster 与 C# 那些事机器视觉是 C# 上位机方案里比较有技术含量的一块。这个方向最常被问到的问题不是算法本身而是“C# 和视觉软件怎么互相调”。我在这套题里放了 Halcon 联合编程、VisionMaster 通讯协议、ONNX 模型推理三块内容。4.1 Halcon 联合编程的基本流程Halcon 提供 .NET 接口核心是HSmartWindowControl做显示控件HOperatorSet里的算子做图像处理。C# 调用 Halcon 实现“快速匹配并排除不需要点”这类功能流程是读图或 GrabImage 取图、做模板匹配、拿到匹配点位然后用掩膜把不需要的点剔除。HImage image new HImage(png, image.png); HTuple modelId; HOperatorSet.CreateNccModel(image, 0, 0, 0, 0, use_polarity, out modelId); HOperatorSet.FindNccModel(image, modelId, 0, 0, 0, 0, 0.5, true, 0, out HTuple row, out HTuple col, out HTuple angle, out HTuple score); // 用区域掩膜排除指定矩形内的点 HRegion maskRegion new HRegion(100, 100, 200, 200); HImage masked image.ReduceDomain(maskRegion);这里最容易踩的坑是 Halcon 的版本兼容。HSmartWindowControl有 WinForm 和 WPF 两个版本NuGet 包和已安装的 Halcon 版本对不上时会直接加载失败。建议用 Halcon 自带安装目录里的halcondotnet.dll而不是 NuGet 的默认包并且项目目标平台 x64 要和 Halcon 的位数一致不然运行时报BadImageFormatException。4.2 VisionMaster 与 C# 的通讯协议选型海康的 VisionMaster 软件和 C# 上位机通讯实际项目里主流有三条路TCP/IP 明文协议、Modbus TCP、SDK 二次开发。很多新手问“哪个协议比较好”我的答案是分场景。如果是 C# 自己做上位机同时对实时性要求高直接选 SDK 最稳VisionMaster 提供的二次开发接口可以做到真正意义上的“集成”图像结果能直接以对象形式拿到 C# 里。但是 SDK 版本有强绑定升级 VisionMaster 后 DLL 也要跟着升这个维护成本要想清楚。如果只是把 VisionMaster 当成独立检测单元C# 通过协议拿结果推荐 TCP/IP。流程是 C# 发指令帧比如触发拍照、请求结果VisionMaster 检测完把 OK/NG 结果和坐标以固定格式回传。字段和数据用 JSON 或 XML 都行但建议用版本号字段方便以后扩展。Modbus TCP 则更适合有 PLC 的产线C# 只需要把视觉结果映射到寄存器地址PLC 直接读寄存器就行。所以不是哪个“更好”而是看 C#、VisionMaster、PLC 谁和谁通信。4.3 ONNX 模型推理Sketch 素描模型怎么和 C# 结合C# 里跑 ONNX 模型首选Microsoft.ML.OnnxRuntime这个 NuGet 包。网上有人用 C# 调素描模型做图像风格化原理是一样的加载 ONNX、准备输入 tensor、跑推理、解析输出。using var session new InferenceSession(sketch.onnx); var input new DenseTensorfloat(new[] { 1, 3, 512, 512 }); // 填充像素数据... var inputs new ListNamedOnnxValue { NamedOnnxValue.CreateFromTensor(input, input) }; using var results session.Run(inputs); var output results.First().AsTensorfloat(); // 把输出 tensor 转回图像有几个关键点。第一预处理必须和训练时一致比如归一化是除以 255 还是均值方差错了结果会完全不对。第二输入尺寸要按模型的固定尺寸来比如 512x512 就 resize 成 512x512别贪方便直接传原图。第三如果连续做大批量推理InferenceSession建议复用不要每次 new性能差很多。最后输出 tensor 到图像如果是分类模型输出就是标签概率如果是素描模型输出通常是单通道的灰度图把它转成 Bitmap 显示时记得处理像素值范围经常要乘一个系数才看得见。5. Web 与混合方向MVC、Vue、JObject 与异步处理C# 不止有上位机Web 后端同样是重头戏。100 例里安排了不少 Web 相关的题目核心原则是解决“前后端怎么衔接”和“大数据量怎么不卡死”这两类问题。5.1 MVC 项目如何优雅集成 Vue“C# MVC 项目支持 Vue”有三种做法各有利弊。第一种是完全前后端分离Vue 项目独立开发部署MVC 只提供 Web API跨域问题用 CORS 解决。第二种是 MVC 视图内嵌 Vue也就是 Razor 页面引 Vue 的 CDN 或打包后的 JS 文件这种方案上手最快适合在现有系统里局部改造。第三种是用 Node 中间件做集成把 Vue 的 dev server 代理给 ASP.NET开发体验好但部署复杂。我的建议是如果是老项目改造别一上来就搞前后端分离。先保证 API 层能用 RESTful 风格暴露再在 Razor 视图里局部引入 Vue 做交互组件这样迁移风险最小。这里要记住一个原则MVC 的防伪令牌AntiForgeryToken和 Vue 的 Ajax 请求要配合好。如果直接用 Vue 发 POST 但没有带RequestVerificationTokenMVC 会直接 400 拒绝请求。解决办法是在布局页把 token 渲染成一个隐藏字段Vue 每次请求都从表单里取出来放到 header 里。5.2 Swagger UI 增加账号密码访问Swagger 是接口文档神器但生产环境把 Swagger 裸奔出来等于把接口结构全暴露了。给 Swagger UI 加账号密码访问最简单的方案是加一个中间件在 Swagger 的请求路径上做 Basic 认证。app.UseSwaggerUI(c { c.SwaggerEndpoint(/swagger/v1/swagger.json, My API V1); }); app.UseWhen(context context.Request.Path.StartsWithSegments(/swagger), appBuilder { appBuilder.Use(async (context, next) { var auth context.Request.Headers[Authorization].ToString(); // 校验 user:pass 的 Base64错误则返回 401 }); });这个方案比自定义ISwaggerProvider要简单很多也不影响 Swagger 本身的逻辑。注意一点Basic 认证走的是 HTTP Header如果服务是 HTTP 明文传输账号密码等于裸奔生产环境建议挂 HTTPS。还有人把认证逻辑写在Configure最前面结果把整个 API 都拦了这也是常见问题所以一定要用UseWhen只匹配/swagger路径。5.3 后台处理前端传过来的 Excel从同步卡死到异步任务“C# 后台处理前端传过来的 Excel”这个需求看起来简单实际坑很多。最典型的是直接用HttpContext.Request.Body去读读完发现 Body 是空的。这是因为Request.Body默认只能读一次而且某些中间件已经消费过了。解决办法是提前启用缓冲Request.EnableBuffering(); using var reader new StreamReader( Request.Body, Encoding.UTF8, leaveOpen: true); string body await reader.ReadToEndAsync(); Request.Body.Position 0;另一个坑是 Excel 文件不能同步处理。比如导入 5 万行同步处理会让接口几十秒不返回前端要么超时要么以为崩了。标准做法是接口只接收文件并扔进后台队列返回一个任务 ID后台任务慢慢处理前端轮询任务状态。处理 Excel 的库我优先推荐ClosedXML或EPPlus前者 API 友好、后者性能好NPOI虽然免费但写起来啰嗦适合维护老项目时才用。5.4 JObject 与 JSON 解析的坑用 Newtonsoft.Json 解析 JObject 时判断某个字段是否存在很多新手直接写jObject[lffh] null结果字段存在但是值为null字符串或者是个空数组[]判断就失效了。正确姿势是用TryGetValueJObject obj JObject.Parse(json); if (obj.TryGetValue(lffh, out JToken? value) value.Type ! JTokenType.Null) { // 字段存在且非空 }另外一个经验如果字段的值是从其他系统传过来的别假设它一定是字符串。数字、布尔、对象、数组都会出现。稳妥的做法是先把JToken转成一个明确的类型比如value?.Type JTokenType.Array再遍历而不是祈祷它长得和文档一样。6. 高频问题与面试经验文本框、数值变化检测、必考题最后这部分是 100 例里容易被忽略但实战价值极高的“小需求”题和处理经验。它们单个看都不难但组合起来就是“做过”和“没做过”的区别。6.1 文本框失去焦点与变量数值变化检测文本框失去焦点是最常见的交互需求比如输入完数字自动格式化、校验范围。WinForm 里可以用Validating和Validated事件配合ErrorProvider做提示WPF 里则用绑定和验证规则更干净。一个很容易踩的坑是在Leave事件里直接修改文本框的值会再次触发Leave造成递归。我一般用一个标志位或者在Validated事件里修改并关闭验证。检测变量数值变化工业项目里经常用。比如读取到某个点位值从 0 变 1要触发一个动作。最笨的是写死轮询比较上一次值能用但代码很散。我推荐实现INotifyPropertyChanged或者给变化检测封装一个方法public class ValueMonitor { private float _lastValue; public event Actionfloat? ValueChanged; public void Update(float newValue) { if (Math.Abs(newValue - _lastValue) float.Epsilon) { _lastValue newValue; ValueChanged?.Invoke(newValue); } } }这样把“检测变化”和“响应变化”解耦后续加历史记录、报警都方便。6.2 面试必问但实战也容易翻车的几个知识点面试题里 C# 方向问来问去逃不出几个点。我最常给读者强调的是下面这张表每个点都对应一个容易翻车的细节。考点考察的核心容易翻车的地方值类型 vs 引用类型struct/class 在栈和堆上的分配差异以为 int 在方法里改了会影响外部装箱/拆箱值类型转 object 发生了什么循环里大量ArrayList.Add(1)性能爆炸string 不可变为什么拼接用 StringBuilder误以为 string 是引用类型就能原地修改ref/out/in参数传递方式in 参数是只读引用不能给其赋值async/await 死锁同步上下文阻塞UI 线程.Result直接死锁要ConfigureAwait(false)lock 对象选择锁的粒度对string或this加锁容易锁错对象LINQ 延迟执行IQueryable/IEnumerable 的区别以为Where之后集合变少了其实没执行这里重点说in参数。它是只读引用传递适合传大 struct 时避免拷贝但很多人因为加了in就在方法里尝试给参数赋值编译直接报错。另外async/await死锁这个问题我在 WinForm 上位机里遇到过很多次UI 线程调用.Result等异步方法完成但异步方法的继续要回到 UI 线程两边互相等界面就卡死了。解法是异步方法里尽早ConfigureAwait(false)或者调用方也用await。整套 100 例刷下来我自己最大的体会是C# 的知识点不是孤立的字符串题能接到 JSON 解析反射题能接到插件架构Socket 题能接到扫码枪扫码枪又和 UI 刷新卡顿串在一起。真正值钱的不是背下某个 API而是知道“遇到哪类问题该从哪个方向下手”。如果说这套题能给你留一个印象我希望是任何复杂项目都能拆成“数据从哪来、怎么传输、怎么处理、怎么显示”四个环节逐层去解就不会慌。本文还有配套的精品资源点击获取
返回列表