C# 上位机委托 & 事件详解(异步通信与 UI 更新实战版)
在 C# 上位机开发中,委托 & 事件是实现 “通信层与 UI 层解耦”、“异步数据回调” 的核心机制,尤其在串口 / 网口异步通信、多设备数据反馈、UI 实时更新场景中不可或缺。本文将从委托基础到事件实战,结合工控上位机场景,详解委托 & 事件的使用逻辑、Action/Func泛型委托的简化技巧,以及如何通过它们实现通信层与 UI 层的解耦和异步 UI 更新。
一、 委托:定义方法模板,实现方法 “间接调用”
1. 委托的核心定义(工控场景解读)
委托(Delegate)是 C# 中的类型安全的方法指针,本质是 “方法模板的定义”—— 它规定了方法的返回值类型、参数列表格式,任何符合该格式的方法都可以被委托引用,实现 “通过委托间接调用方法”,无需直接依赖方法所属类。
对于上位机开发,委托的核心价值:
- 解耦:通信层(如串口类)无需知道 UI 层的具体更新方法,只需通过委托调用,实现两层分离;
- 灵活:一个委托可以引用多个方法(多播委托),实现 “一处触发,多处响应”(如串口接收数据后,同时更新 UI、存储数据库、触发报警);
- 异步支撑:配合异步操作(如
Task),实现异步通信数据的回调通知(如串口异步接收数据后,通过委托回调到 UI 层)。
2. 委托的基础使用:自定义委托(理解方法模板)
先通过自定义委托理解核心逻辑,再过渡到Action/Func泛型委托的简化使用。
(1) 自定义委托语法
// 定义委托:规定方法模板(返回值void,参数:int(设备ID)、string(数据内容))
// 对应工控场景:串口接收数据后,传递设备ID和数据内容给UI层
public delegate void SerialDataReceivedDelegate(int deviceId, string data);
(2) 工控场景实战:串口通信层使用自定义委托
using System;
using System.IO.Ports;
namespace UpperComputerDelegateEvent
{
/// <summary>
/// 串口通信层(底层:仅负责数据收发,不关心UI层如何处理)
/// </summary>
public class SerialCommunicator
{
private SerialPort _serialPort;
// 声明委托字段:用于引用符合模板的方法(如UI层的更新方法)
public SerialDataReceivedDelegate? OnDataReceived;
public SerialCommunicator(string portName, int baudRate)
{
_serialPort = new SerialPort
{
PortName = portName,
BaudRate = baudRate,
Parity = Parity.None,
DataBits = 8,
StopBits = StopBits.One,
ReadTimeout = 1000,
WriteTimeout = 1000
};
// 绑定串口异步接收数据事件
_serialPort.DataReceived += SerialPort_DataReceived;
}
/// <summary>
/// 打开串口
/// </summary>
public bool OpenPort()
{
try
{
if (!_serialPort.IsOpen)
{
_serialPort.Open();
return true;
}
return false;
}
catch (Exception ex)
{
Console.WriteLine($"串口打开失败:{ex.Message}");
return false;
}
}
/// <summary>
/// 关闭串口
/// </summary>
public bool ClosePort()
{
try
{
if (_serialPort.IsOpen)
{
_serialPort.Close();
return true;
}
return false;
}
catch (Exception ex)
{
Console.WriteLine($"串口关闭失败:{ex.Message}");
return false;
}
}
/// <summary>
/// 串口异步接收数据(底层逻辑)
/// </summary>
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
// 读取串口接收的数据
string recvData = _serialPort.ReadExisting();
int deviceId = 1; // 模拟设备ID
// 委托不为空时,间接调用引用的方法(如UI层更新方法)
OnDataReceived?.Invoke(deviceId, recvData);
}
catch (Exception ex)
{
Console.WriteLine($"数据接收失败:{ex.Message}");
}
}
}
}
(3) UI 层绑定委托,实现数据更新
/// <summary>
/


463

被折叠的 条评论
为什么被折叠?



