第三节、C# 上位机面向对象编程详解(工控硬件封装实战版)

在上位机开发中,面向对象编程(OOP)并非单纯的语法特性,而是解决硬件管理混乱、代码冗余的核心手段。其核心价值在于将零散的硬件操作(参数配置、数据读取、设备启停)封装为独立的类,实现代码复用、逻辑解耦,更能轻松支撑多设备扩展(如从单温度传感器扩展至数十个多类型传感器集群)。下面结合工控实战场景,全面拆解面向对象在硬件封装中的应用。

一、 核心思想:硬件设备 "类" 的抽象建模

工控设备(传感器、PLC、串口模块)都具备两个共性:固有属性 (不可或很少变动的参数)和行为操作(可执行的功能),这恰好对应面向对象的 "属性" 和 "方法",也是硬件封装的核心逻辑。

面向对象概念 工控硬件对应关系 示例(温度传感器)
类(Class) 硬件设备的抽象模板 TemperatureSensor(温度传感器类,定义所有温度传感器的通用属性和方法)
属性(Property) 硬件固有参数 / 状态 端口号、波特率、设备 ID、当前温度值、连接状态等
方法(Method) 硬件可执行操作 连接设备、断开连接、读取温度、校准参数、启停采集等
对象(Object) 硬件设备的具体实例 sensor1 = new TemperatureSensor(1, "COM3", 9600)(1 号温度传感器,对应 COM3 端口)

二、 实战封装:温度传感器类(完整可复用代码)

以串口温度传感器为例,演示如何通过面向对象封装实现硬件管理,代码兼顾实用性和可扩展性,可直接嵌入上位机项目。

1. 完整封装代码

cs 复制代码
using System;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;

namespace UpperComputerOOP
{
    /// <summary>
    /// 温度传感器类(封装串口温度传感器的所有属性和操作)
    /// 特点:高内聚(属性+方法封装为一体)、低耦合(与UI层解耦,通过事件反馈数据)
    /// </summary>
    public class TemperatureSensor
    {
        #region 1. 封装设备属性(硬件参数+状态)
        // 私有字段:仅类内部可访问,保障数据安全性
        private SerialPort _serialPort; // 串口对象(私有,外部无需直接操作)
        private int _sensorId; // 传感器ID(唯一标识,用于多设备区分)
        private string _portName; // 串口名称(如COM3)
        private int _baudRate; // 波特率
        private float _currentTemp; // 当前温度值
        private bool _isConnected; // 连接状态
        private CancellationTokenSource _cts; // 用于停止采集线程
        private Task _collectTask; // 采集任务

        // 公共属性:外部可访问(部分只读,防止非法修改)
        public int SensorId => _sensorId; // 只读属性:传感器ID不可修改
        public string PortName => _portName; // 只读属性:端口号初始化后不可修改
        public int BaudRate => _baudRate; // 只读属性:波特率初始化后不可修改
        public float CurrentTemp => _currentTemp; // 只读属性:当前温度仅内部采集更新
        public bool IsConnected => _isConnected; // 只读属性:连接状态仅内部更新

        #endregion

        #region 2. 封装事件(实现硬件数据/状态与UI层解耦)
        // 温度数据更新事件(外部订阅,接收温度数据)
        public event Action<int, float> TempUpdated; // 携带:传感器ID、当前温度
        // 设备状态变更事件(外部订阅,接收连接/断开状态)
        public event Action<int, string, bool> StatusChanged; // 携带:传感器ID、端口号、连接状态
        // 异常提示事件(外部订阅,接收错误信息)
        public event Action<int, string> ErrorOccurred; // 携带:传感器ID、错误信息

        #endregion

        #region 3. 构造函数(初始化硬件参数)
        /// <summary>
        /// 构造函数:初始化温度传感器参数
        /// </summary>
        /// <param name="sensorId">传感器唯一ID</param>
        /// <param name="portName">串口名称(如COM3)</param>
        /// <param name="baudRate">波特率(如9600)</param>
        public TemperatureSensor(int sensorId, string portName, int baudRate)
        {
            // 初始化私有字段
            _sensorId = sensorId;
            _portName = portName;
            _baudRate = baudRate;
            _currentTemp = 0.0f;
            _isConnected = false;
            _cts = new CancellationTokenSource();

            // 初始化串口对象
            _serialPort = new SerialPort
            {
                PortName = portName,
                BaudRate = baudRate,
                Parity = Parity.None,
                DataBits = 8,
                StopBits = StopBits.One,
                ReadTimeout = 1000, // 读取超时时间
                WriteTimeout = 1000  // 写入超时时间
            };

            // 绑定串口数据接收事件(内部处理,不暴露给外部)
            _serialPort.DataReceived += SerialPort_DataReceived;
        }

        #endregion

        #region 4. 封装设备方法(硬件操作:连接、断开、读取温度、启停采集)
        /// <summary>
        /// 连接温度传感器
        /// </summary>
        /// <returns>连接是否成功</returns>
        public bool Connect()
        {
            try
            {
                if (!_isConnected && !_serialPort.IsOpen)
                {
                    _serialPort.Open(); // 打开串口
                    _isConnected = true;
                    // 触发状态变更事件,通知外部
                    StatusChanged?.Invoke(_sensorId, _portName, _isConnected);
                    return true;
                }
                return _isConnected;
            }
            catch (Exception ex)
            {
                string errorMsg = $"传感器{_sensorId}({_portName})连接失败:{ex.Message}";
                // 触发异常事件,通知外部
                ErrorOccurred?.Invoke(_sensorId, errorMsg);
                return false;
            }
        }

        /// <summary>
        /// 断开温度传感器连接
        /// </summary>
        /// <returns>断开是否成功</returns>
        public bool Disconnect()
        {
            try
            {
                // 先停止采集任务
                StopCollect();

                if (_isConnected && _serialPort.IsOpen)
                {
                    _serialPort.Close(); // 关闭串口
                    _isConnected = false;
                    // 触发状态变更事件,通知外部
                    StatusChanged?.Invoke(_sensorId, _portName, _isConnected);
                    return true;
                }
                return !_isConnected;
            }
            catch (Exception ex)
            {
                string errorMsg = $"传感器{_sensorId}({_portName})断开失败:{ex.Message}";
                ErrorOccurred?.Invoke(_sensorId, errorMsg);
                return false;
            }
        }

        /// <summary>
        /// 启动温度自动采集
        /// </summary>
        /// <param name="interval">采集间隔(毫秒)</param>
        public void StartCollect(int interval = 1000)
        {
            if (!_isConnected)
            {
                ErrorOccurred?.Invoke(_sensorId, $"传感器{_sensorId}未连接,无法启动采集");
                return;
            }

            if (_collectTask == null || _collectTask.IsCompleted)
            {
                _cts = new CancellationTokenSource();
                CancellationToken token = _cts.Token;

                // 后台任务执行采集,不阻塞UI
                _collectTask = Task.Run(() =>
                {
                    while (!token.IsCancellationRequested)
                    {
                        ReadTempManually(); // 手动读取温度
                        Thread.Sleep(interval); // 采集间隔
                    }
                }, token);
            }
        }

        /// <summary>
        /// 停止温度自动采集
        /// </summary>
        public void StopCollect()
        {
            if (_cts != null && !_cts.IsCancellationRequested)
            {
                _cts.Cancel(); // 取消采集任务
            }
        }

        /// <summary>
        /// 手动读取一次温度(单次采集)
        /// </summary>
        /// <returns>读取到的温度值</returns>
        public float ReadTempManually()
        {
            try
            {
                if (!_isConnected || !_serialPort.IsOpen)
                {
                    throw new Exception("传感器未连接,无法读取温度");
                }

                // 1. 发送读取温度指令(Modbus-RTU 03功能码示例,可根据硬件协议修改)
                byte[] readCmd = BuildModbusReadCmd();
                _serialPort.Write(readCmd, 0, readCmd.Length);

                // 2. 此处已通过SerialPort_DataReceived事件解析温度,返回当前缓存值
                return _currentTemp;
            }
            catch (Exception ex)
            {
                string errorMsg = $"传感器{_sensorId}读取温度失败:{ex.Message}";
                ErrorOccurred?.Invoke(_sensorId, errorMsg);
                return 0.0f;
            }
        }

        #endregion

        #region 5. 内部辅助方法(不暴露给外部,封装细节)
        /// <summary>
        /// 内部方法:组装Modbus-RTU读取温度指令(硬件协议细节,外部无需关心)
        /// </summary>
        /// <returns>读取指令字节数组</returns>
        private byte[] BuildModbusReadCmd()
        {
            // 模拟Modbus-RTU 03功能码指令(从站地址01,读取起始寄存器0000,寄存器数量0001)
            byte[] cmd = new byte[8];
            cmd[0] = 0x01; // 从站地址(对应传感器ID,可按需修改)
            cmd[1] = 0x03; // 03功能码:读取保持寄存器
            cmd[2] = 0x00; // 起始寄存器高8位
            cmd[3] = 0x00; // 起始寄存器低8位
            cmd[4] = 0x00; // 寄存器数量高8位
            cmd[5] = 0x01; // 寄存器数量低8位
            // 计算CRC16校验(此处简化,可替换为真实CRC算法)
            cmd[6] = 0x84;
            cmd[7] = 0x0A;
            return cmd;
        }

        /// <summary>
        /// 串口数据接收事件(内部处理,解析温度数据)
        /// </summary>
        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                int byteCount = _serialPort.BytesToRead;
                byte[] recvData = new byte[byteCount];
                _serialPort.Read(recvData, 0, byteCount);

                // 解析温度数据(模拟解析,可根据硬件真实协议修改)
                // 此处假设返回数据为4个字节,解析为float类型温度
                if (recvData.Length >= 4)
                {
                    byte[] tempBytes = new byte[4];
                    Array.Copy(recvData, 2, tempBytes, 0, 4); // 截取温度数据字节

                    // 处理字节序(工控设备多为大端序,Windows为小端序)
                    if (BitConverter.IsLittleEndian)
                    {
                        Array.Reverse(tempBytes);
                    }

                    _currentTemp = BitConverter.ToSingle(tempBytes, 0); // 更新当前温度
                    // 触发温度更新事件,通知外部(如UI层显示)
                    TempUpdated?.Invoke(_sensorId, _currentTemp);
                }
            }
            catch (Exception ex)
            {
                string errorMsg = $"传感器{_sensorId}数据解析失败:{ex.Message}";
                ErrorOccurred?.Invoke(_sensorId, errorMsg);
            }
        }

        #endregion

        #region 6. 资源释放(防止内存泄漏)
        /// <summary>
        /// 释放串口资源
        /// </summary>
        public void Dispose()
        {
            Disconnect(); // 先断开连接
            _serialPort?.Dispose(); // 释放串口资源
            _cts?.Dispose(); // 释放取消令牌资源
        }
        #endregion
    }
}

2. 封装核心亮点(工控场景适配)

  1. 访问控制合理 :用private修饰内部细节(串口对象、校验方法),public只读属性暴露设备参数,防止外部非法修改(如误改端口号导致通信异常);
  2. 解耦设计 :通过事件(TempUpdated/StatusChanged)实现硬件层与 UI 层的解耦,传感器无需知道 UI 层如何显示数据,UI 层只需订阅事件即可接收数据,符合 "开闭原则";
  3. 细节隐藏 :硬件协议细节(如 Modbus 指令组装、字节序处理)封装在内部辅助方法中,外部调用者无需关心协议细节,只需调用Connect()/ReadTempManually()即可,降低使用门槛;
  4. 资源安全 :提供Dispose()方法释放串口、取消令牌资源,防止程序退出后端口占用或内存泄漏;
  5. 异常容错 :所有硬件操作(连接、读取、断开)都包含异常捕获,并通过ErrorOccurred事件反馈错误信息,便于 UI 层提示用户,提升程序稳定性。

三、 实战应用:多温度传感器管理(体现代码复用性)

封装后的传感器类可快速扩展至多设备场景,无需重复编写串口配置、数据读取等代码,只需创建多个对象即可实现多设备管理。

1. 多传感器使用代码

cs 复制代码
using System;

namespace UpperComputerOOP
{
    class Program
    {
        static void Main(string[] args)
        {
            // 1. 创建3个温度传感器实例(代码复用,只需传入不同参数)
            TemperatureSensor sensor1 = new TemperatureSensor(1, "COM3", 9600);
            TemperatureSensor sensor2 = new TemperatureSensor(2, "COM4", 9600);
            TemperatureSensor sensor3 = new TemperatureSensor(3, "COM5", 9600);

            // 2. 统一订阅事件(所有传感器共用事件处理逻辑,进一步复用代码)
            sensor1.TempUpdated += OnTempUpdated;
            sensor1.StatusChanged += OnStatusChanged;
            sensor1.ErrorOccurred += OnErrorOccurred;

            sensor2.TempUpdated += OnTempUpdated;
            sensor2.StatusChanged += OnStatusChanged;
            sensor2.ErrorOccurred += OnErrorOccurred;

            sensor3.TempUpdated += OnTempUpdated;
            sensor3.StatusChanged += OnStatusChanged;
            sensor3.ErrorOccurred += OnErrorOccurred;

            // 3. 统一操作多传感器(连接、启动采集)
            sensor1.Connect();
            sensor2.Connect();
            sensor3.Connect();

            sensor1.StartCollect(1000); // 1秒采集一次
            sensor2.StartCollect(1000);
            sensor3.StartCollect(1000);

            // 4. 保持程序运行,观察数据
            Console.WriteLine("多传感器采集已启动,按任意键停止...");
            Console.ReadKey();

            // 5. 统一停止采集、断开连接、释放资源
            sensor1.StopCollect();
            sensor2.StopCollect();
            sensor3.StopCollect();

            sensor1.Disconnect();
            sensor2.Disconnect();
            sensor3.Disconnect();

            sensor1.Dispose();
            sensor2.Dispose();
            sensor3.Dispose();
        }

        #region 统一事件处理逻辑
        /// <summary>
        /// 所有传感器的温度更新事件处理(UI层可在此处更新显示)
        /// </summary>
        private static void OnTempUpdated(int sensorId, float temp)
        {
            Console.WriteLine($"【温度更新】传感器{sensorId}:{temp:F2}℃");
        }

        /// <summary>
        /// 所有传感器的状态变更事件处理(UI层可在此处更新状态显示)
        /// </summary>
        private static void OnStatusChanged(int sensorId, string portName, bool isConnected)
        {
            string status = isConnected ? "已连接" : "已断开";
            Console.WriteLine($"【状态变更】传感器{sensorId}({portName}):{status}");
        }

        /// <summary>
        /// 所有传感器的异常事件处理(UI层可在此处弹窗提示错误)
        /// </summary>
        private static void OnErrorOccurred(int sensorId, string errorMsg)
        {
            Console.WriteLine($"【异常提示】传感器{sensorId}:{errorMsg}");
        }
        #endregion
    }
}

2. 多设备扩展优势

  1. 零冗余代码 :新增传感器只需一行代码new TemperatureSensor(4, "COM6", 9600),无需重复编写串口配置、数据解析等逻辑;
  2. 统一管理:所有传感器共用一套事件处理逻辑,便于集中监控、统一控制(如批量连接、批量停止采集);
  3. 易于维护 :若硬件协议变更(如 Modbus 指令修改),只需修改TemperatureSensor类的内部辅助方法,无需修改所有设备的调用代码,降低维护成本;
  4. 可扩展性强 :基于继承特性,可快速扩展不同类型传感器(如HumiditySensor(湿度传感器)继承自基础Sensor类),实现多类型设备统一管理。

四、 面向对象核心原则在硬件封装中的体现

面向对象原则 工控硬件封装应用
封装性 隐藏硬件协议细节、串口操作细节,仅暴露简洁的属性和方法,降低外部使用复杂度
继承性 定义基础Sensor抽象类,封装所有传感器的通用属性(ID、端口号)和方法(Connect、Disconnect),温度 / 湿度 / 压力传感器继承该类,复用通用逻辑
多态性 不同传感器的ReadData()方法(读取数据)有不同实现,外部可通过Sensor基类统一调用,无需区分传感器类型
开闭原则 新增传感器类型时,无需修改原有代码,只需新增继承类,符合 "对扩展开放,对修改关闭"

五、 总结

  1. 面向对象在 C# 上位机中的核心价值是硬件封装:将设备属性(端口号、波特率)和操作(连接、读取数据)封装为类,实现代码复用与逻辑清晰;
  2. 封装关键要点:合理使用访问修饰符(private隐藏细节、public暴露接口)、通过事件实现解耦、包含异常容错与资源释放,提升程序稳定性;
  3. 多设备扩展优势:基于封装后的类,可快速实现多传感器 / 多 PLC 管理,大幅减少冗余代码,降低维护成本,是工业级上位机开发的必备技能;
  4. 实战落地:本文提供的TemperatureSensor类可直接复用,只需根据实际硬件协议修改指令组装与数据解析逻辑,即可快速对接真实温度传感器。
相关推荐
散峰而望3 小时前
【算法竞赛】C++入门(三)、C++输入输出初级 -- 习题篇
c语言·开发语言·数据结构·c++·算法·github
行思理3 小时前
css 样式新手教程
前端·css·html5
kingwebo'sZone3 小时前
c# 遍历 根据控件名获取控件实例
开发语言·c#
星空椰3 小时前
jvms Java 版本管理工具
java·开发语言
REDcker3 小时前
C++ 崩溃堆栈捕获库详解
linux·开发语言·c++·tcp/ip·架构·崩溃·堆栈
qq_406176143 小时前
JavaScript闭包:从底层原理到实战
开发语言·前端·javascript
沐知全栈开发3 小时前
`.toggleClass()` 方法详解
开发语言
冰暮流星3 小时前
javascript之Math对象——绝对值,开次方,四舍五入
前端·javascript
啊啊啊啊懒3 小时前
vite创建完项目之后vue文件中有标签报错
前端·javascript·vue.js