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

在工控上位机开发中,单一硬件封装(如温度传感器)已无法满足复杂项目需求(多类型传感器、PLC、仪表共存)。继承作为面向对象的核心特性,能够实现通用硬件逻辑的复用,统一设备管理接口,大幅降低多类型硬件的开发与维护成本。本文将以工控场景为核心,详解继承在硬件封装中的实战应用,从基础抽象类设计到多子类扩展,实现 "一套接口管理所有设备"。

一、 继承的核心价值:工控硬件的 "通用逻辑复用"

工控领域的硬件(温度传感器、湿度传感器、压力传感器、串口 PLC)存在大量通用属性与通用操作,如果每个设备都单独封装,会产生大量冗余代码(如串口配置、连接 / 断开逻辑、状态反馈等)。

继承的核心价值在于:

  1. 抽取通用逻辑:将所有硬件的通用属性(设备 ID、端口号、波特率、连接状态)和通用方法(连接、断开、资源释放)抽取到 ** 基类(父类)** 中;
  2. 实现子类扩展:不同硬件(温度 / 湿度传感器)继承基类,只需实现自身独有的逻辑(如温度解析、湿度解析),无需重复编写通用代码;
  3. 统一管理接口:通过基类引用管理所有子类对象,实现 "不区分设备类型,统一调用方法",简化多设备管理逻辑。

工控硬件通用特性梳理

通用属性(所有硬件共享) 通用操作(所有硬件共享) 独有逻辑(不同硬件差异)
设备唯一 ID(SensorId) 设备连接(Connect) 数据解析逻辑(温度 / 湿度 / 压力)
串口名称(PortName) 设备断开(Disconnect) 专属指令组装(读取温度 / 写入 PLC 寄存器)
波特率(BaudRate) 资源释放(Dispose) 数据类型转换(float 温度 /int 湿度)
连接状态(IsConnected) 状态反馈(StatusChanged) 校准逻辑(温度校准 / 压力零点校准)
异常信息(ErrorMsg) 异常反馈(ErrorOccurred) -

二、 实战第一步:设计硬件抽象基类(SensorBase)

基类分为两种:普通基类 (可实例化)和抽象基类 (不可实例化,仅作为子类模板)。工控场景中,优先使用抽象基类(abstract class),因为 "传感器" 是一个抽象概念,只有具体的温度 / 湿度传感器才有实际意义,抽象基类可强制子类实现独有逻辑,避免接口不统一。

1. 抽象基类完整代码(通用逻辑封装)

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

namespace UpperComputerOOPInherit
{
    /// <summary>
    /// 工控硬件抽象基类(所有串口设备的父类)
    /// 特点:1. 封装通用属性与方法;2. 定义抽象方法,强制子类实现独有逻辑;3. 通过事件实现与UI层解耦
    /// </summary>
    public abstract class SensorBase : IDisposable
    {
        #region 1. 通用属性(所有串口硬件共享,子类可直接访问)
        // 私有字段:通用硬件参数
        private int _sensorId; // 设备唯一ID
        private string _portName; // 串口名称
        private int _baudRate; // 波特率
        private bool _isConnected; // 连接状态
        protected SerialPort _serialPort; // 串口对象(protected:子类可访问,外部不可访问)
        protected CancellationTokenSource _cts; // 采集取消令牌(子类可访问)
        protected Task _collectTask; // 采集任务(子类可访问)

        // 公共只读属性:外部可访问,不可修改
        public int SensorId => _sensorId;
        public string PortName => _portName;
        public int BaudRate => _baudRate;
        public bool IsConnected => _isConnected;

        // 保护属性:子类可修改,外部不可访问
        protected bool IsCollecting { get; set; } = false; // 是否正在采集

        #endregion

        #region 2. 通用事件(所有硬件共享,统一反馈状态/数据/异常)
        /// <summary>
        /// 设备状态变更事件(连接/断开)
        /// </summary>
        public event Action<int, string, bool> StatusChanged;

        /// <summary>
        /// 异常发生事件
        /// </summary>
        public event Action<int, string> ErrorOccurred;

        /// <summary>
        /// 通用数据更新事件(携带设备ID和数据对象,适配所有硬件数据类型)
        /// </summary>
        public event Action<int, object> DataUpdated;

        #endregion

        #region 3. 构造函数(初始化通用硬件参数,子类调用)
        /// <summary>
        /// 基类构造函数:初始化串口硬件通用参数
        /// </summary>
        /// <param name="sensorId">设备唯一ID</param>
        /// <param name="portName">串口名称(COM3/COM4等)</param>
        /// <param name="baudRate">波特率(9600/115200等)</param>
        public SensorBase(int sensorId, string portName, int baudRate)
        {
            _sensorId = sensorId;
            _portName = portName;
            _baudRate = baudRate;
            _isConnected = false;

            // 初始化通用串口配置(所有串口硬件共享)
            _serialPort = new SerialPort
            {
                PortName = portName,
                BaudRate = baudRate,
                Parity = Parity.None,
                DataBits = 8,
                StopBits = StopBits.One,
                ReadTimeout = 1000,
                WriteTimeout = 1000
            };

            _cts = new CancellationTokenSource();
            // 绑定通用串口数据接收事件(子类可重写或复用)
            _serialPort.DataReceived += SerialPort_DataReceived;
        }

        #endregion

        #region 4. 通用方法(所有硬件共享,子类可直接使用,无需重复编写)
        /// <summary>
        /// 通用连接方法(所有串口硬件共用同一套连接逻辑)
        /// </summary>
        /// <returns>连接是否成功</returns>
        public virtual bool Connect()
        {
            try
            {
                if (!_isConnected && !_serialPort.IsOpen)
                {
                    _serialPort.Open();
                    _isConnected = true;
                    // 触发状态变更事件
                    StatusChanged?.Invoke(_sensorId, _portName, _isConnected);
                    OnConnectSuccess(); // 子类可重写该方法,实现连接成功后的专属逻辑
                    return true;
                }
                return _isConnected;
            }
            catch (Exception ex)
            {
                string errorMsg = $"设备{_sensorId}({_portName})连接失败:{ex.Message}";
                ErrorOccurred?.Invoke(_sensorId, errorMsg);
                return false;
            }
        }

        /// <summary>
        /// 通用断开方法(所有串口硬件共用同一套断开逻辑)
        /// </summary>
        /// <returns>断开是否成功</returns>
        public virtual bool Disconnect()
        {
            try
            {
                // 通用逻辑:先停止采集
                StopCollect();

                if (_isConnected && _serialPort.IsOpen)
                {
                    _serialPort.Close();
                    _isConnected = false;
                    // 触发状态变更事件
                    StatusChanged?.Invoke(_sensorId, _portName, _isConnected);
                    OnDisconnectSuccess(); // 子类可重写该方法,实现断开后的专属逻辑
                    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 virtual void StartCollect(int interval = 1000)
        {
            if (!_isConnected)
            {
                ErrorOccurred?.Invoke(_sensorId, $"设备{_sensorId}未连接,无法启动采集");
                return;
            }

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

                // 通用后台采集任务(子类只需实现具体的数据读取逻辑)
                _collectTask = Task.Run(() =>
                {
                    while (!token.IsCancellationRequested && IsCollecting)
                    {
                        ReadDataManually(); // 调用抽象方法,由子类实现具体逻辑
                        Thread.Sleep(interval);
                    }
                }, token);
            }
        }

        /// <summary>
        /// 通用停止采集方法(所有硬件共用)
        /// </summary>
        public virtual void StopCollect()
        {
            if (IsCollecting)
            {
                IsCollecting = false;
                if (_cts != null && !_cts.IsCancellationRequested)
                {
                    _cts.Cancel();
                }
            }
        }

        /// <summary>
        /// 通用资源释放方法(所有硬件共用,防止内存泄漏/端口占用)
        /// </summary>
        public virtual void Dispose()
        {
            Disconnect(); // 先断开连接
            _serialPort?.Dispose(); // 释放串口资源
            _cts?.Dispose(); // 释放取消令牌资源
            GC.SuppressFinalize(this);
        }

        #endregion

        #region 5. 抽象方法(强制子类实现,保证接口统一,体现独有逻辑)
        /// <summary>
        /// 抽象方法:手动读取数据(子类必须实现,对应各自的硬件数据读取逻辑)
        /// </summary>
        /// <returns>读取到的具体数据(object类型,适配所有数据类型)</returns>
        public abstract object ReadDataManually();

        /// <summary>
        /// 抽象方法:组装硬件专属指令(子类必须实现,如温度读取指令、湿度读取指令)
        /// </summary>
        /// <returns>专属指令字节数组</returns>
        protected abstract byte[] BuildHardwareCmd();

        #endregion

        #region 6. 虚方法(子类可选择性重写,扩展通用逻辑)
        /// <summary>
        /// 虚方法:连接成功后的扩展逻辑(子类可重写,默认无实现)
        /// </summary>
        protected virtual void OnConnectSuccess()
        {
            // 通用逻辑可在此扩展,子类也可重写覆盖
        }

        /// <summary>
        /// 虚方法:断开成功后的扩展逻辑(子类可重写,默认无实现)
        /// </summary>
        protected virtual void OnDisconnectSuccess()
        {
            // 通用逻辑可在此扩展,子类也可重写覆盖
        }

        /// <summary>
        /// 通用串口数据接收事件(子类可重写,实现专属数据解析)
        /// </summary>
        protected virtual void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            // 通用数据接收逻辑:读取字节流(子类可重写该方法,实现专属解析)
            try
            {
                int byteCount = _serialPort.BytesToRead;
                byte[] recvData = new byte[byteCount];
                _serialPort.Read(recvData, 0, byteCount);
                // 子类重写时,可在此处添加专属数据解析逻辑
                OnDataReceived(recvData);
            }
            catch (Exception ex)
            {
                string errorMsg = $"设备{_sensorId}数据接收失败:{ex.Message}";
                ErrorOccurred?.Invoke(_sensorId, errorMsg);
            }
        }

        /// <summary>
        /// 虚方法:数据接收后的扩展逻辑(子类可重写,实现专属解析)
        /// </summary>
        /// <param name="recvData">接收的字节数组</param>
        protected virtual void OnDataReceived(byte[] recvData)
        {
            // 子类重写该方法,实现数据解析
        }

        #endregion
    }
}

2. 基类设计关键要点

  1. 访问修饰符合理分配
    • private:通用字段(_sensorId_isConnected),仅基类内部访问,防止外部非法修改;
    • protected:串口对象(_serialPort)、取消令牌(_cts),子类可直接访问,无需通过属性传递,简化子类开发;
    • public:通用方法(ConnectDisconnect)、只读属性(SensorIdIsConnected),外部调用者可访问,统一设备操作接口;
    • abstract:抽象方法(ReadDataManuallyBuildHardwareCmd),强制子类实现,保证所有设备接口统一;
    • virtual:虚方法(OnConnectSuccessSerialPort_DataReceived),子类可选择性重写,兼顾通用逻辑与子类扩展。
  2. 事件统一化 :定义StatusChanged(状态变更)、ErrorOccurred(异常反馈)、DataUpdated(数据更新)三个通用事件,所有子类共享同一套事件机制,UI 层可统一订阅,无需区分设备类型。
  3. 资源安全保障 :实现IDisposable接口,提供通用Dispose方法,自动释放串口、取消令牌资源,防止程序退出后端口占用或内存泄漏。

三、 实战第二步:子类继承基类,实现专属硬件逻辑

基于SensorBase抽象基类,我们分别实现 ** 温度传感器(TemperatureSensor)湿度传感器(HumiditySensor)** 两个子类,只需关注自身独有逻辑(数据解析、指令组装),无需重复编写通用逻辑。

1. 子类 1:温度传感器(TemperatureSensor)

cs 复制代码
using System;

namespace UpperComputerOOPInherit
{
    /// <summary>
    /// 温度传感器子类(继承自SensorBase抽象基类)
    /// 只需实现独有逻辑:温度指令组装、温度数据解析
    /// </summary>
    public class TemperatureSensor : SensorBase
    {
        // 独有字段:当前温度值
        private float _currentTemp;

        // 独有属性:当前温度(外部只读)
        public float CurrentTemp => _currentTemp;

        /// <summary>
        /// 子类构造函数:调用基类构造函数,初始化通用参数
        /// </summary>
        /// <param name="sensorId">传感器ID</param>
        /// <param name="portName">串口名称</param>
        /// <param name="baudRate">波特率</param>
        public TemperatureSensor(int sensorId, string portName, int baudRate)
            : base(sensorId, portName, baudRate) // 必须调用基类构造函数
        {
            _currentTemp = 0.0f;
        }

        #region 实现抽象方法(独有逻辑:强制实现)
        /// <summary>
        /// 实现抽象方法:手动读取温度数据
        /// </summary>
        /// <returns>当前温度(object类型,可自动装箱)</returns>
        public override object ReadDataManually()
        {
            try
            {
                if (!IsConnected)
                {
                    throw new Exception("温度传感器未连接,无法读取温度");
                }

                // 调用子类独有指令组装方法
                byte[] readCmd = BuildHardwareCmd();
                // 复用基类的串口对象,无需重复初始化
                _serialPort.Write(readCmd, 0, readCmd.Length);

                // 返回当前温度(object类型,适配基类通用事件)
                return _currentTemp;
            }
            catch (Exception ex)
            {
                string errorMsg = $"温度传感器{SensorId}读取失败:{ex.Message}";
                ErrorOccurred?.Invoke(SensorId, errorMsg);
                return 0.0f;
            }
        }

        /// <summary>
        /// 实现抽象方法:组装温度传感器专属Modbus-RTU指令
        /// </summary>
        /// <returns>温度读取指令字节数组</returns>
        protected override byte[] BuildHardwareCmd()
        {
            // 温度传感器独有指令:Modbus-RTU 03功能码,读取温度寄存器
            byte[] cmd = new byte[8];
            cmd[0] = (byte)SensorId; // 从站地址=传感器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;
        }

        #endregion

        #region 重写虚方法(扩展逻辑:选择性重写)
        /// <summary>
        /// 重写虚方法:数据接收后,解析温度数据(独有逻辑)
        /// </summary>
        /// <param name="recvData">接收的字节数组</param>
        protected override void OnDataReceived(byte[] recvData)
        {
            base.OnDataReceived(recvData); // 可选:调用基类通用逻辑

            // 温度传感器独有解析逻辑:4字节转float温度值
            if (recvData.Length >= 4)
            {
                byte[] tempBytes = new byte[4];
                Array.Copy(recvData, 2, tempBytes, 0, 4);

                // 工控必备:处理字节序(硬件大端序→系统小端序)
                if (BitConverter.IsLittleEndian)
                {
                    Array.Reverse(tempBytes);
                }

                // 更新子类独有字段:当前温度
                _currentTemp = BitConverter.ToSingle(tempBytes, 0);
                // 触发基类通用数据更新事件,UI层统一接收
                DataUpdated?.Invoke(SensorId, _currentTemp);
            }
        }

        /// <summary>
        /// 重写虚方法:温度传感器连接成功后的专属逻辑(如校准初始化)
        /// </summary>
        protected override void OnConnectSuccess()
        {
            // 子类专属逻辑:连接成功后,初始化温度校准参数
            string msg = $"温度传感器{SensorId}连接成功,已初始化校准参数";
            Console.WriteLine(msg);
            // 可在此添加校准逻辑
        }

        #endregion
    }
}

2. 子类 2:湿度传感器(HumiditySensor)

cs 复制代码
using System;

namespace UpperComputerOOPInherit
{
    /// <summary>
    /// 湿度传感器子类(继承自SensorBase抽象基类)
    /// 只需实现独有逻辑:湿度指令组装、湿度数据解析
    /// </summary>
    public class HumiditySensor : SensorBase
    {
        // 独有字段:当前湿度值
        private int _currentHumidity;

        // 独有属性:当前湿度(外部只读)
        public int CurrentHumidity => _currentHumidity;

        /// <summary>
        /// 子类构造函数:调用基类构造函数,初始化通用参数
        /// </summary>
        /// <param name="sensorId">传感器ID</param>
        /// <param name="portName">串口名称</param>
        /// <param name="baudRate">波特率</param>
        public HumiditySensor(int sensorId, string portName, int baudRate)
            : base(sensorId, portName, baudRate) // 调用基类构造函数
        {
            _currentHumidity = 0;
        }

        #region 实现抽象方法(独有逻辑:强制实现)
        /// <summary>
        /// 实现抽象方法:手动读取湿度数据
        /// </summary>
        /// <returns>当前湿度(object类型)</returns>
        public override object ReadDataManually()
        {
            try
            {
                if (!IsConnected)
                {
                    throw new Exception("湿度传感器未连接,无法读取湿度");
                }

                // 调用子类独有指令组装方法
                byte[] readCmd = BuildHardwareCmd();
                // 复用基类串口对象
                _serialPort.Write(readCmd, 0, readCmd.Length);

                return _currentHumidity;
            }
            catch (Exception ex)
            {
                string errorMsg = $"湿度传感器{SensorId}读取失败:{ex.Message}";
                ErrorOccurred?.Invoke(SensorId, errorMsg);
                return 0;
            }
        }

        /// <summary>
        /// 实现抽象方法:组装湿度传感器专属Modbus-RTU指令
        /// </summary>
        /// <returns>湿度读取指令字节数组</returns>
        protected override byte[] BuildHardwareCmd()
        {
            // 湿度传感器独有指令:Modbus-RTU 03功能码,读取湿度寄存器
            byte[] cmd = new byte[8];
            cmd[0] = (byte)SensorId; // 从站地址=传感器ID
            cmd[1] = 0x03; // 03功能码:读取保持寄存器
            cmd[2] = 0x00; // 起始寄存器高8位
            cmd[3] = 0x01; // 起始寄存器低8位(与温度传感器不同,独有逻辑)
            cmd[4] = 0x00; // 寄存器数量高8位
            cmd[5] = 0x01; // 寄存器数量低8位
            // CRC16校验
            cmd[6] = 0xC4;
            cmd[7] = 0x0B;
            return cmd;
        }

        #endregion

        #region 重写虚方法(扩展逻辑:选择性重写)
        /// <summary>
        /// 重写虚方法:数据接收后,解析湿度数据(独有逻辑)
        /// </summary>
        /// <param name="recvData">接收的字节数组</param>
        protected override void OnDataReceived(byte[] recvData)
        {
            base.OnDataReceived(recvData);

            // 湿度传感器独有解析逻辑:2字节转int湿度值
            if (recvData.Length >= 4)
            {
                // 拼接高低位字节
                _currentHumidity = (recvData[2] << 8) | recvData[3];
                // 触发基类通用数据更新事件
                DataUpdated?.Invoke(SensorId, _currentHumidity);
            }
        }

        #endregion
    }
}

子类设计关键要点

  1. 构造函数必须调用基类 :子类通过: base(sensorId, portName, baudRate)调用基类构造函数,初始化通用参数(设备 ID、串口信息),无需重复编写初始化逻辑。
  2. 强制实现抽象方法 :必须实现ReadDataManually(数据读取)和BuildHardwareCmd(指令组装)两个抽象方法,保证所有子类接口统一,基类可通过抽象方法调用子类独有逻辑。
  3. 选择性重写虚方法 :针对OnDataReceived(数据解析)、OnConnectSuccess(连接后扩展)等虚方法,子类可根据需求重写,实现独有逻辑,不影响基类通用功能。
  4. 复用基类资源 :子类可直接访问基类的_serialPort(串口对象)、IsConnected(连接状态)等protected成员,无需重复创建,大幅减少冗余代码。

四、 实战第三步:基于继承的多设备统一管理

通过继承,所有硬件都实现了SensorBase基类的统一接口,上位机可通过基类引用管理所有子类对象,无需区分设备类型(温度 / 湿度传感器),实现 "一套代码管理所有设备"。

1. 多设备统一管理代码

cs 复制代码
using System;
using System.Collections.Generic;

namespace UpperComputerOOPInherit
{
    class Program
    {
        // 用基类列表存储所有子类对象,实现统一管理
        private static List<SensorBase> _sensorList = new List<SensorBase>();

        static void Main(string[] args)
        {
            // 1. 创建不同类型传感器对象(子类实例),添加到基类列表
            TemperatureSensor tempSensor1 = new TemperatureSensor(1, "COM3", 9600);
            TemperatureSensor tempSensor2 = new TemperatureSensor(2, "COM4", 9600);
            HumiditySensor humiSensor1 = new HumiditySensor(3, "COM5", 9600);
            HumiditySensor humiSensor2 = new HumiditySensor(4, "COM6", 9600);

            // 基类列表存储子类对象(多态特性)
            _sensorList.Add(tempSensor1);
            _sensorList.Add(tempSensor2);
            _sensorList.Add(humiSensor1);
            _sensorList.Add(humiSensor2);

            // 2. 统一订阅所有设备的通用事件(无需区分设备类型)
            foreach (var sensor in _sensorList)
            {
                sensor.StatusChanged += OnSensorStatusChanged;
                sensor.DataUpdated += OnSensorDataUpdated;
                sensor.ErrorOccurred += OnSensorErrorOccurred;
            }

            // 3. 统一操作所有设备(连接、启动采集)
            Console.WriteLine("开始统一连接所有设备...");
            foreach (var sensor in _sensorList)
            {
                sensor.Connect(); // 统一调用基类Connect方法
            }

            Console.WriteLine("\n开始统一启动所有设备采集...");
            foreach (var sensor in _sensorList)
            {
                sensor.StartCollect(1000); // 统一调用基类StartCollect方法
            }

            // 保持程序运行
            Console.WriteLine("\n多设备采集已启动,按任意键停止...");
            Console.ReadKey();

            // 4. 统一操作所有设备(停止采集、断开连接、释放资源)
            Console.WriteLine("\n开始统一停止采集并断开所有设备...");
            foreach (var sensor in _sensorList)
            {
                sensor.StopCollect(); // 统一调用基类StopCollect方法
                sensor.Disconnect(); // 统一调用基类Disconnect方法
                sensor.Dispose(); // 统一调用基类Dispose方法
            }

            _sensorList.Clear();
            Console.WriteLine("所有设备已断开并释放资源,程序退出...");
        }

        #region 统一事件处理逻辑(无需区分设备类型)
        /// <summary>
        /// 所有设备的状态变更统一处理
        /// </summary>
        private static void OnSensorStatusChanged(int sensorId, string portName, bool isConnected)
        {
            string status = isConnected ? "已连接" : "已断开";
            Console.WriteLine($"【状态变更】设备{sensorId}({portName}):{status}");
        }

        /// <summary>
        /// 所有设备的数据更新统一处理
        /// </summary>
        private static void OnSensorDataUpdated(int sensorId, object data)
        {
            // 区分设备类型(可选),显示对应数据
            var tempSensor = _sensorList.Find(s => s is TemperatureSensor && s.SensorId == sensorId);
            var humiSensor = _sensorList.Find(s => s is HumiditySensor && s.SensorId == sensorId);

            if (tempSensor != null)
            {
                float temp = (float)data;
                Console.WriteLine($"【数据更新】温度传感器{sensorId}:{temp:F2}℃");
            }
            else if (humiSensor != null)
            {
                int humi = (int)data;
                Console.WriteLine($"【数据更新】湿度传感器{sensorId}:{humi}%RH");
            }
        }

        /// <summary>
        /// 所有设备的异常统一处理
        /// </summary>
        private static void OnSensorErrorOccurred(int sensorId, string errorMsg)
        {
            Console.WriteLine($"【异常提示】设备{sensorId}:{errorMsg}");
        }
        #endregion
    }
}

2. 多设备管理优势(继承带来的核心价值)

  1. 接口统一 :所有设备都通过基类的ConnectDisconnectStartCollect等方法操作,无需针对温度 / 湿度传感器编写不同的操作逻辑,简化代码复杂度。
  2. 扩展便捷 :新增设备类型(如压力传感器PressureSensor)时,只需继承SensorBase基类,实现抽象方法,即可直接加入基类列表,无需修改现有管理代码(符合 "开闭原则")。
  3. 维护成本低 :若通用逻辑需要修改(如串口连接超时时间调整),只需修改SensorBase基类的Connect方法,所有子类自动生效,无需逐个修改子类。
  4. 多态特性体现 :基类列表存储子类对象,调用基类方法时,自动执行子类的实现逻辑(如ReadDataManually),实现 "一个接口,多种实现"。

五、 继承在工控开发中的进阶应用

1. 多层继承:抽象基类→通用串口设备→具体设备

对于更复杂的项目(串口设备 + 网口设备共存),可采用多层继承:

plaintext

复制代码
// 第一层:顶级抽象基类(所有设备通用)
public abstract class DeviceBase { /* 设备ID、状态、事件等通用属性 */ }

// 第二层:串口设备基类(继承DeviceBase,封装串口通用逻辑)
public abstract class SerialDeviceBase : DeviceBase { /* 串口配置、串口读写等逻辑 */ }

// 第三层:网口设备基类(继承DeviceBase,封装TCP/UDP通用逻辑)
public abstract class NetworkDeviceBase : DeviceBase { /* IP、端口、网络读写等逻辑 */ }

// 第四层:具体设备(继承对应二级基类)
public class TemperatureSensor : SerialDeviceBase { /* 温度传感器独有逻辑 */ }
public class PlcTcp : NetworkDeviceBase { /* 网口PLC独有逻辑 */ }

2. 抽象类 vs 接口:工控场景选型

类型 适用场景 工控应用示例
抽象类(abstract) 存在大量通用属性 / 方法,需要复用代码 所有串口传感器(共享串口配置、连接逻辑)
接口(interface) 仅需统一接口,无通用代码复用,支持多实现 可同时支持串口 / 网口的设备(IConnectable接口)

示例:IConnectable接口,统一所有设备的连接接口

cs 复制代码
/// <summary>
/// 连接接口:统一串口/网口设备的连接接口
/// </summary>
public interface IConnectable
{
    bool Connect();
    bool Disconnect();
    bool IsConnected { get; }
}

// 串口传感器继承抽象类+实现接口
public class TemperatureSensor : SerialDeviceBase, IConnectable { }

// 网口PLC继承抽象类+实现接口
public class PlcTcp : NetworkDeviceBase, IConnectable { }

六、 总结

  1. 继承核心价值 :在 C# 上位机工控开发中,继承的核心是通用逻辑复用接口统一,将所有硬件的通用属性 / 方法封装到抽象基类,子类只需实现独有逻辑,大幅减少冗余代码。
  2. 基类设计要点 :优先使用抽象基类,合理分配访问修饰符(private/protected/public),定义通用事件与虚方法,提供资源释放机制,兼顾复用性与扩展性。
  3. 子类设计要点 :通过: base()调用基类构造函数,强制实现抽象方法(保证接口统一),选择性重写虚方法(实现独有逻辑),复用基类的protected成员。
  4. 多设备管理:通过基类列表存储子类对象,利用多态特性实现统一操作与事件处理,新增设备类型无需修改现有代码,符合工业级上位机的扩展性要求。
  5. 实战落地 :本文提供的SensorBase基类及子类代码可直接复用,只需根据实际硬件协议修改指令组装与数据解析逻辑,即可快速对接真实工控设备。
相关推荐
光泽雨7 小时前
ST语言与C#语言数据类型对比详解
开发语言·c#
kylezhao201911 小时前
第三节、C# 上位机面向对象编程详解(工控硬件封装实战版)
开发语言·前端·c#
kingwebo'sZone11 小时前
c# 遍历 根据控件名获取控件实例
开发语言·c#
flysh0513 小时前
深度解析 C# 核心:类(Class)的设计精髓与高级特性
开发语言·c#
莫生灬灬14 小时前
VueMultiBrowser - 开源多浏览器管理器
运维·开发语言·chrome·c#·自动化·vue
William_cl14 小时前
【C# ASP.NET】局部视图 @Html.Partial 全解析:复用 UI 的正确姿势(附避坑指南)
c#·html·asp.net
未来之窗软件服务14 小时前
幽冥大陆(八十八 ) 操作系统应用封装技术C#自解压 —东方仙盟练气期
java·前端·c#·软件打包·仙盟创梦ide·东方仙盟·阿雪技术观
kylezhao20191 天前
C# 语言基础(变量、数据类型、流程控制、面向对象编程)
开发语言·计算机视觉·c#·visionpro
翩若惊鸿_1 天前
【无标题】
开发语言·c#