C#高级语法
学习内容:泛型;反射;委托、Lambda表达式、事件、特性以及Linq扩展;线程与进程;异步编程等。
一、泛型
用处极大,上位机 & 视觉通用
-
统一封装相机类、设备类、检测结果类,一套代码适配不同相机 / 不同产品
-
数据集合存储:检测数据、良品不良集合、参数集合
-
封装通用串口 / 网口收发工具类,不用重复写重载
-
数据库通用增删改查,所有视觉数据通用操作
总结:写通用工具类必备,减少重复代码
二、反射
-
动态加载不同视觉算法类,切换检测方案不用改代码
-
动态读取配置文件 (XML/JSON) 自动赋值实体参数
-
插件式架构:外接检测模块、相机驱动动态加载
-
上位机动态绑定参数面板,自动生成调试界面
场景
:多机型通用视觉软件必备
三、委托 + Lambda + 事件
委托
-
视觉检测回调结果:拍照完成、检测完成触发回调
-
设备动作回调:气缸到位、传感器信号到位执行视觉抓拍
-
通用算法委托,灵活切换匹配 / 测量 / 识别函数
Lambda
-
快速筛选图像数据、筛选 NG 产品、筛选日志
-
简化 Halcon/OpenCV 图像遍历、坐标筛选
-
界面简洁绑定事件,代码更短
事件(最常用)
-
相机拍照完成事件
-
检测异常报警事件
-
PLC 信号触发拍照事件
-
上位机界面按钮、状态变更、日志推送事件
视觉项目核心通信触发全靠事件
四、特性 (Attribute)
-
给视觉参数加标注:参数范围、是否调试可见、单位注释
-
配置文件自动序列化忽略字段
-
权限特性:限定管理员才能修改视觉阈值
-
日志自动标记检测类型、工位编号
多用于规范项目、大型视觉框架
五、Linq & 扩展方法
-
快速筛选不良品数据、重复产品、异常图像
-
批量处理图像坐标、点位数据排序去重
-
简化集合查询、统计良品率、产量统计
-
写视觉工具扩展方法:图像转灰度、ROI 快速截取
日常写代码最高频,极度提升开发速度
六、线程 & 进程
线程(视觉重中之重)
-
采集线程:单独线程持续取相机图像,不卡界面
-
检测线程:后台跑算法,上位机界面流畅不卡顿
-
多工位视觉:一个线程对应一个工位检测
-
日志写入、数据保存单独开线程,不阻塞流程
进程
-
主视觉程序 + 独立相机采集进程分离
-
第三方视觉程序 (Halcon 工具、标定工具) 调用外部进程
-
防止算法崩溃直接卡死整个上位机
没有多线程 = 做不了工业实时视觉项目
七、异步编程 async/await(现在主流)
-
异步读取相机图像、异步读写数据库
-
异步串口 / 网口收发 PLC 数据,不阻塞主线程
-
异步保存图片、异步导出报表
-
大图像预处理、深度学习推理异步执行
新一代视觉上位机全部用异步,淘汰老多线程写法
精简求职优先级(必背)
最高频必精通
委托、事件、Lambda、Linq、多线程、异步
(做视觉上位机 90% 场景在用)
-
常用必会
泛型、XML/JSON 序列化、扩展方法
-
进阶项目用
反射、特性、进程
一句话总结整套体系
界面事件触发 → 异步 / 多线程采图 → Lambda+Linq 处理图像数据 → 委托回调算法结果 → 泛型统一设备管理 → 反射动态配置参数 → 异步存库上报 PLC
这就是完整机器视觉上位机开发逻辑
细讲
泛型
基础讲解
很实用,也用的多。
自带泛型类:list和Dictionary
进阶语法用Winform讲。
输错不会报错 TryParse
逻辑的特点:处理逻辑相同,只是目标类型不同而已。
泛型特点/用用场景--只要符合就能用:逻辑相同或相似,只是传入参数类型或返回值类型不同而已。
object是所有类型基类,也是泛型。
不管值类型还是引用类型都可以调用GetType方法,这个方法就是返回实例。
一种泛指,不指代具体类型。
泛型达到极致必须和反射配合。
传入方法参数可以推断。
泛型类
!取反
泛型方法
不同类型需求用同一个方法解决。
定义泛型方法带<T>,类型方法,类型参数 里面写方法。传入参数是不确定类型
习惯用T,里面写过程,泛型方法具有通用性。
不针对具体逻辑。提供对象把对象代入进去。
传入实参能自动推算
返回值类型是类型参数,传入类型不能直接推断。
定义某一类型封装泛型类,参照具体类而言,把内部方法由逐个转变为泛型过程,直到所有类型都以类型参数来代替的过程。
有些类型对当前类中的方法不适用,不适用的情况下给一个范围限定。
定义泛型方法: 1.设计哪些类型。 2.适当给些约束(值类型无效给引用类型约束)值类型约束引用类型不可去限定值类型约束。 3.会存在基类派生子类情况。
可以针对不同类型用同一个泛型类来处理。
针对不同类用泛型类封装,类里面封装细节基本一样。
ArrayList 也要了解重要
相对于固定数组更加灵活,方便存取。
建议使用list列表
ArrayList可以解决固定长度数组。
里面项是object,不推荐使用。针对某一个项可以用但不推荐。添加一个值类型有装箱和拆箱损耗,不推荐使用。List<T>类型集合列表,强类型转换但不会有性能损耗。
添加修改移除都可以操作。
不建议使用这个集合操作,里面项不是强类型,是弱类型。添加项取出来有装箱和拆箱性能损耗。
引用类型是某一个class类型。
拆箱、移除项、判断某一个项是否在集合里面 获取某一个项的位置索引 集合排序功能 翻转功能(从里面指定位置的数目翻转到指定功能)。
索引号移除
里面的项是强类型不是弱类型,任意一个项都可以往里面加。
List
数组作为什么用,它就做什么用。
数据集合存储为什么选list不选数组:里面元素操作尤其是项的增减用list列表更灵活更好操作。
泛型集合列表,通过索引访问对象。
强类型列表,类型集合一样,是安全的。比Arraylist好,类型安全。
存储值类型
遍历集合中可以改值但不能移除里面的项会报错因为在枚举过程中。
添加项;在指定位置插入一个项;添加一个集合;添加多个项;移除项;移除指定位置处的项;判断某个项是否存在;查找某个指定项;查找某个项指定位置索引;查找符合条件的项索引;查找某个指定项位置;对这组信息排序(针对这种值类型);集合中的项前后翻转;清空这个集合中的项。
常见:添加项;插入项;添加一组;移除项;移除指定位置的项;判断存在,Find指定项;查找某个项所在位置;偶尔涉及符合项指定条件位置;后面三个涉及到存在。
思考List什么时候用。
颜色变淡:可以不写可以省略。--自动推断。
泛型类型t
规则理解性记忆。描述准确就行。
Dictionary<TKey,TValue>(泛型集合类--键值对集合,强类型集合)重要
键和值一一对应。
使用常见。
每次都是成对添加。
键和值可以是字符串可以是值类型,对象,集合。
键和值:可以是值,也可以是字符串,可以是整形,也可以是对象。
使用频率比较高的集合。
判断是否存在最后一个添加
对应的值不是统一类型才用哈希表。
一一对应关系用键值对集合。
泛型约束
更严谨加泛型约束。
5种约束限定泛型约束范围。
限定类型范围--泛型约束
基类约束也是引用类型约束。
无参构造在最后一个约束。
值类型约束加where(限定类型范围)
自带引用类型必须是class
泛型类型指定类型本身就是基类约束。
调用是限制指定类型实际范围--泛型约束。
掌握约束规则。
基类约束:实际调用类型约束必须是基类。 值类型约束与引用类型约束不能共存。基类约束和引用类型约束不能同时共用。 接口约束可以和基类约束共存。 定义泛型方法用基类约束也可以。
几个相似,类里面和引用方法里面适用就行。
T派生struct
类里面类型一样,使用方法差不多才用引用类型和值类型约束。
基类约束
限制类型参数,实际类型本身必须是基类本身或它的子类或派生类。这个类型才能用于基类。应用类型派生于类型本身或它的应用。
基类约束不能与引用类型约束同时设置。
接口约束
给一个泛型方法或泛型类,调用时候必须实现接口中的方法。
调用实际类型必须是现接口。可以和基类约束共存。
创建一个接口,定义一个接口方法。
不可行原因
加上生成实现接口
加上不报错还有结果输出
基类约束在前接口在后,调用时实现接口中方法就可以。
实现接口后调用就没问题。
做接口实现后再创建接口类型对象,然后调用泛型方法就可以。
应用方法实际类型必须实现接口。
无参构造约束
通过构造时加一个new构造参数实例的方式。
可以通过泛型方法通过new实例创建一个实例。
提供不确定类型创建实例通过无参构造函数来做。
根据谁创建谁的实例
同时出现(基类约束,接口约束,无参构造) 先基类,后接口,new 永远排最后。
泛型类+new后面加T--无参构造约束
接口约束:指定的实际类型必须要实现接口。不然会违反接口约束。基类约束必须在第一个,无参构造函数函数特殊。
如果要写new必须在前
where T : [基类约束], [接口1, 接口2, ...], new()
值类型约束只能限定值类型,引用类型约束只能限定引用类型,基类约束只针对基类本身或派生类可以作为实际类型,接口约束限定使用实际类型必须实现接口方法,无参构造函数限定可以通过无参构造方式来进行实例化。
不能和引用约束共用,不能和值约束共用。
5个约束理解清楚就好。
通过反射方式可以直接获取构造函数,构造函数无法通过代码直接调用。
反射
程序集文件类型名可以生成动态程序集。程序集文件类型名 = 动态程序集的 "文件名 / 扩展名标识
通过反射获取程序集名称的细节。定义的方法和细节都可以看。通过类型名了解所有细节。
程序可以访问、检测和修改它本身状态或行为的一种能力。
之前通过new实例方式创建。
可以动态创建实例,可以获取某一类型表中对象的类型信息。
元数据(类中成员里面成员和成员信息)指的是里面成员。通过反射获取类成员信息和成员数据。
不需要知道源代码,只知道类型或类型完整字符串名称就可以
有反射了解成员的内部信息和成员数据。
获取到类中详细细节的过程。
几个类记忆一下。(获取数据类型;程序集内部信息或某一类型的成员;通过类型快速对对象实例化)--几个类常用
是什么?做什么?几个类?--记忆
常用的公有属性和公有成员通过反射来获取,私有的一般不推荐。反射只碰公开对外的,私有属于内部隐私,别强行用反射硬抠,既不规范、又不稳定、还不安全。
记忆反射用途。
通过dll文件动态加载起来。
反射用途记忆。
通过反射和泛型封装类库。结合泛型和反射封装数据访问通用底层,信息基本对象增删改查把基本逻辑对象封装起来。
基本和通用方法逻辑结合泛型和反射通用封装,使用中代入具体类型就可以调用封装方法去实现增删改查操作。不至于每个类把具体逻辑都写一次。
学会用反射去获取某一类型的基本数据去读写对象属性值在对象里面动态加载程序集动态创建实例。
不能直接用引用类型或具体使用某一个具体类型,不知道引用哪个用反射动态实例化。
动态实例化1 实例化2
实例化类获取完整名称,通过Type对象创建实例。 完整名称:命名空间名+类名
创建本地、远程对象的方法 --Activator
完整名称创建实例,直接通过类型的Type对象来创建实例。
不直接使用类型和直接使用类型(Ce)内部还是调用无参构造函数来实例化。
创建方法本质就是调用无参构造函数。
不能直接使用类型,在泛型方法里面不确定类型实例利用动态实例化方式Aer
查看类型元数据--通过反射
查看属性和字段
获取类型中细节:1.查看元数据获取属性。
属性值:通过直接代码获取。
属性名:通过属性数组获取。
属性值获取
修改属性值
get和set方法取值改值
获取公开属性(取值和改值)
私有字段不是公开是实例。
获取属性和获取字段单个获取和多个获取一样。方法名不同,形式名一样,
元数据--方法及调用
静态方法是类成员不是实例成员。
构造函数及调用
创建类或对象时候直接调用一个构造函数。
反射用方法形式调用。
获取方法,传入调用方法,调用是实际类型Type对象,执行调用方法后强转。实例object类型需要强转。
通过反射创建构造函数及调用构造函数创建对象。
反射与用过new调用结果一样。 本质结果一样,过程不一样。
动态加载程序集--反射方式
平常是调用某一个库或项目里面东西先在当前项目引用给添加进来就能引用另一个项目里面类型或调用里面方法。
不直接引用--反射:拿到要直接引用项目的dll然后通过程序代码方式动态加载起来,然后可以获取里面类型,调用里面方法。
Debug下dll文件
程序集名称和dll文件名一样,传字符串名称。
加载后可以通过程序集获取里面类型。
注释后删除
生成dll内部项目 除WPF外生成的是可执行文件。
三层架构及其它层丢掉内库项目里面。
反射与泛型结合应用
不确定类型封装成类型参数T,后面直接代入实际类型用特定类型进行处理。
区别:类型不一样,类里面细节不一样,所做逻辑相同或类似,反映通用型处理。--用反射与泛型结合。
经常用这种方式定义一些类型相同逻辑的处理基类,包含一些公共逻辑。
反射经常与泛型结合做通用应用。
反射优缺点总结
以前有多少类型写多少次。
通过反射把以前写的类型现在就用一份逻辑实现,调用用一个逻辑实现相同类型来应用。
相同应用类型用第一点
代入实际不同类型即可
代入不同类型实现目标类,无序提前目标代码硬编。
绕过直接代码过程,效率慢直接代码,小的没必要。通过直接代码写通用逻辑操作(灵活性和拓展性) 多段代码用同一个逻辑用反射。没重用性。
维护性问题--调试不直接使用。逻辑性相对于直接代码会模糊内部逻辑,比直接代码更复杂,合理选择。
没有应用于多个类型情况程序灵活性或扩展性有更高需求的时候不建议用反射。希望看到直接源码不是通用性反射逻辑不要用反射。
反射用在通用性和拓展性基础上更强调灵活。源代码更直接程序逻辑更加清晰不要用反射。
通用性大,逻辑性和类型不同,更强调灵活性用反射。
根据场景和需求更合理应用。--灵活性和扩展性高用反射。
缺点:直接执行上绕过直接代码没有直接代码好。模糊内部逻辑。
原则:该用可以用,没用就不用。
属性读写操作
字段操作和属性操作基本一样。
方法成员:无参方法、有参方法、带参方法。
熟悉基本操作使用。
不为空调用ToString()
泛型方法通过反射调用就有麻烦。
无参不需要传参,传null
通过反射方式不怎么常用。一般都是直接写方式。
通过反射调用实例方法用null
记忆这三点:加载外部 DLL;反射创建类实例;反射调用方法、读写属性
委托
委托就是方法引用。
多线程执行逻辑封装到一个类里,就是委托。
多个方法链接到一起就是多播委托。
Lamaba表达式通过匿名函数引用过来。
没返回值用void
new实例化和赋值委托调用
任何一个方法调用都可以通过委托合并为实例化对象。
通过一个委托合并进行多个调用,多播委托。
Lambda表达式
匿名函数的另外一种编程形式。
一条:一条语句。 多条:语句块+{ }
左边:参数列表。 右边:执行主体。
Action委托--封装无返回值方法
可以带泛型也可以不带泛型
无Action委托封装没有泛型方法。
泛型里面可以传入6个参数无返回值。
Func委托--封装带返回值方法
典型泛型委托。
可以没返回值参数可以最多传16个参数。
最后有return语句
事件
事件发生时处理事件运行的逻辑。
事件声明先有委托。
委托和事件区别:委托可以在内部调也可以在外部调,事件只能在发布器里面调。
调用事件后必须触发。
调用事件方法:在发布器里提供触发事件的方法 提供触发点在什么时候调用。
订阅:接收事件。
订阅事件:处理与接收程序关联起来。
事件响应会执行调用器中的方法,也就是逻辑处理程序。
订阅事件方法:首先有一个委托,通过委托定义和调用事件,提供触发方法,订阅事件,最后通过操作区触发事件去执行事件,达到事件应用效果。
添加过后进行刷新,刷新过后进行调用。
事件触发:订阅 触发点。
特性
程序运行时传递元素行为信息的标签。 类中属性、字段都可以用。
运行时运动反射技术利用编程方式反馈。
自定义特性主讲。
类或类中成员。--特性属性第二条。
属性名与字段名不一致加一个真实的列名来表示。 实体类属性和列名表示。
语法:特性名+真实类名
某一个方法想放弃用新的方法用Obsolete
自定义特性有简单需求。
直接定义属性+一个构造函数==构建自定义特性
不用写Attritbute
通过反射方式去访问。
有特性加注释就是注释文本,没加注释就是属性文本。
Linq
Linq介绍与查询语法
1.数据源。 2.创建查询。 3.执行查询(遍历时候才会执行)。
Linq查询子句详解
一种类型应用成另一种类型:投影。
分组输出及分组统计用group by
数字集合去重方便。
多线程基础
进程:操作系统的资源分配最小运行单元。
多线程有点识记。
按钮控制逻辑都有主线程创建。
回调:要执行的任务。
线程池:执行任务排进去就行。
Task--多任务,多线程开启子线程另一种方式
简单好用--更简洁,更简洁
<T>带返回值
做进度过程用子线程。
开启子任务和子线程的三种方式(1-3)
阻塞:卡界面。
这种就是卡界面,最好别用。
等待任何一个任务完成--不会阻塞
用户界面体验更好--WhenAny 等待任何一个任务完成,不会阻塞。
这个不会卡
加await等待执行完成后再走。 配对async使用
回调就是委托。
一个可等,一个异步执行。
异步调用
异步方法通过异步调用不会卡住当前线程,程序会 "同时干别的事",等结果回来了再继续执行。
使用异步方法配对
async(标记一个方法是异步方法,在异步方法内才能调用)和awint是异步调用关键字。
执行异步的不一定是子线程,很可能是主线程。
异步方法内才能进行异步调用。
子任务完成后会销毁。
界面控件:WPF/Winform
文本框多层控件一定要加。
异步:跳过不中断。
begin立刻返回。
多线程很复杂多任务会做。
主线程执行执行完后会卡,子线程不会。
多线程里面不要把耗时放在主线程。 多线程异常会超范围。
创建控件的线程就是主线程,委托通过主线程执行。
放在for不放在Invoke里主要是等于消耗时间。