如何向签名固定的事件传递含密钥的Device对象?
问题:向固定签名的事件传递自定义对象(蓝牙配对场景)
我需要向一个签名固定的事件传递对象,但无法修改该事件签名(也不能自定义EventArgs)。具体场景是蓝牙配对事件需要传入密钥,这是个通用问题。
当前订阅事件的代码如下:
public async Task MyPairAsync(Device device) { var bluetoothLEDevice = await BluetoothLEDevice.FromBluetoothAddressAsync(device.Id); DeviceInformationCustomPairing customPairing = bluetoothLEDevice.DeviceInformation.Pairing.Custom; customPairing.PairingRequested += CustomPairing_PairingRequested; var result = await customPairing.PairAsync(DevicePairingKinds.ProvidePin); // 后续处理(比如评估配对结果等)... }
Device类包含BLE设备的ID和密钥:
public class Device { public ulong Id { get; } public string Passkey { get; private set; } public Device(ulong id, string passkey) { Id = id; Passkey = passkey; } }
PairingRequested事件的签名是固定的,因此事件处理方法必须遵循该签名:
private void CustomPairing_PairingRequested(DeviceInformationCustomPairing sender, DevicePairingRequestedEventArgs args) { args.Accept(device.Passkey); // 这里无法直接访问device,因为上下文里没有这个对象 }
DevicePairingRequestedEventArgs是密封类,无法继承来扩展携带Device实例。我不想用全局变量来传递Device对象,有没有更优雅的方案?
可行方案
方案1:利用闭包捕获当前Device实例
直接在订阅事件时使用lambda表达式作为处理方法,借助C#闭包特性捕获当前方法内的device变量,同时记得配对完成后及时取消订阅避免内存泄漏。
修改后的代码:
public async Task MyPairAsync(Device device) { var bluetoothLEDevice = await BluetoothLEDevice.FromBluetoothAddressAsync(device.Id); DeviceInformationCustomPairing customPairing = bluetoothLEDevice.DeviceInformation.Pairing.Custom; // 定义局部处理方法,捕获当前device void pairingHandler(DeviceInformationCustomPairing s, DevicePairingRequestedEventArgs args) { args.Accept(device.Passkey); // 处理完成后立即取消订阅 s.PairingRequested -= pairingHandler; } customPairing.PairingRequested += pairingHandler; var result = await customPairing.PairAsync(DevicePairingKinds.ProvidePin); // 保险起见,无论配对结果如何都取消订阅 customPairing.PairingRequested -= pairingHandler; // 后续处理... }
这种方法简洁直接,无需额外存储结构,通过及时取消订阅避免内存泄漏,是单设备配对场景下的最优解。
方案2:用字典映射设备标识与Device实例
如果需要处理多设备并发配对,可以维护一个线程安全的字典,将设备唯一标识(如蓝牙地址)与Device实例关联。在事件处理方法中,从sender获取设备标识,再从字典中取出对应Device对象。
示例代码:
// 用ConcurrentDictionary保证多线程场景下的安全性 private readonly ConcurrentDictionary<ulong, Device> _deviceMap = new ConcurrentDictionary<ulong, Device>(); public async Task MyPairAsync(Device device) { var bluetoothLEDevice = await BluetoothLEDevice.FromBluetoothAddressAsync(device.Id); DeviceInformationCustomPairing customPairing = bluetoothLEDevice.DeviceInformation.Pairing.Custom; // 将设备ID和实例存入字典 _deviceMap.TryAdd(device.Id, device); customPairing.PairingRequested += CustomPairing_PairingRequested; var result = await customPairing.PairAsync(DevicePairingKinds.ProvidePin); // 配对完成后清理字典并取消订阅 customPairing.PairingRequested -= CustomPairing_PairingRequested; _deviceMap.TryRemove(device.Id, out _); // 后续处理... } private void CustomPairing_PairingRequested(DeviceInformationCustomPairing sender, DevicePairingRequestedEventArgs args) { // 从sender提取设备ID并获取对应的Device实例 if (ulong.TryParse(sender.DeviceInformation.Id, out ulong deviceId) && _deviceMap.TryGetValue(deviceId, out Device device)) { args.Accept(device.Passkey); } }
该方案适合批量处理设备配对的场景,通过标识映射关联对象,但需注意及时清理字典内容,防止内存泄漏。
方案3:使用弱引用字典(进阶)
如果担心字典的强引用导致Device实例无法被GC回收,可以改用WeakReference<Device>存储,进一步降低内存泄漏风险:
private readonly ConcurrentDictionary<ulong, WeakReference<Device>> _weakDeviceMap = new ConcurrentDictionary<ulong, WeakReference<Device>>(); public async Task MyPairAsync(Device device) { var bluetoothLEDevice = await BluetoothLEDevice.FromBluetoothAddressAsync(device.Id); DeviceInformationCustomPairing customPairing = bluetoothLEDevice.DeviceInformation.Pairing.Custom; _weakDeviceMap.TryAdd(device.Id, new WeakReference<Device>(device)); customPairing.PairingRequested += CustomPairing_PairingRequested; var result = await customPairing.PairAsync(DevicePairingKinds.ProvidePin); customPairing.PairingRequested -= CustomPairing_PairingRequested; _weakDeviceMap.TryRemove(device.Id, out _); // 后续处理... } private void CustomPairing_PairingRequested(DeviceInformationCustomPairing sender, DevicePairingRequestedEventArgs args) { if (ulong.TryParse(sender.DeviceInformation.Id, out ulong deviceId) && _weakDeviceMap.TryGetValue(deviceId, out var weakRef) && weakRef.TryGetTarget(out Device device)) { args.Accept(device.Passkey); } }
这种方案适合长期运行的应用,避免因强引用导致的内存泄漏,但代码复杂度相对较高。
内容的提问来源于stack exchange,提问作者Thern
相关产品推荐
相关产品推荐

