C# OPC-UA客户端:获取节点复杂类型并实例化对象用于写入
OPC UA 官方NetStandard库直接创建复杂类型实例写入方案
你不需要通过预读节点值的方式获取复杂类型实例,基于已经加载完成的ComplexTypeSystem可以纯本地完成实例创建,无额外读值开销,实现逻辑如下:
- 优先做类型映射缓存:初始化会话并完成
ComplexTypeSystem的类型加载后,将所有需要用到的复杂类型的DataType NodeId和对应动态生成的RuntimeType存入字典缓存,后续操作全程本地执行,不需要额外发起服务端请求。 - 若未提前做缓存,仅需读取目标节点的
DataType基础属性(属于节点元数据,不是读值操作,开销远低于读取完整节点值),拿到对应复杂类型的NodeId后,调用ComplexTypeSystem.GetComplexType(NodeId typeId)方法获取动态生成的运行时类型。 - 所有通过
ComplexTypeSystem加载的RuntimeType都自带无参构造函数,直接调用Activator.CreateInstance(type)即可创建空白实例,通过反射给实例的各个属性赋值后,就可以直接封装成Variant传入写入方法。
参考实现代码
// 初始化阶段执行一次即可,全局缓存类型映射 private Dictionary<NodeId, Type> _complexTypeCache = new Dictionary<NodeId, Type>(); void InitComplexTypeCache(Session session, ComplexTypeSystem cts) { // 可提前批量浏览目标节点的DataType属性,一次性加载所有需要的类型缓存 // 示例:加载已知类型Id的复杂类型 NodeId workOrderTypeId = new NodeId(1025, 2); // 替换为实际业务的类型NodeId _complexTypeCache[workOrderTypeId] = cts.GetComplexType(workOrderTypeId); } // 复杂类型节点写入通用方法 StatusCode WriteComplexTypeNode(Session session, NodeId targetNodeId, Action<object> valueSetter) { // 读取目标节点的DataType元数据,开销极低,该步骤也可提前缓存结果 DataValue typeAttr = session.ReadAttribute(targetNodeId, Attributes.DataType); NodeId typeId = typeAttr.GetValue<NodeId>(NodeId.Null); if (!_complexTypeCache.TryGetValue(typeId, out Type runtimeType)) { throw new InvalidOperationException($"未加载目标节点对应复杂类型,TypeId:{typeId}"); } // 纯本地创建实例,无任何节点读值操作 object instance = Activator.CreateInstance(runtimeType); // 执行业务赋值逻辑 valueSetter(instance); // 发起写入请求 WriteValueCollection writeCollection = new WriteValueCollection { new WriteValue { NodeId = targetNodeId, AttributeId = Attributes.Value, Value = new DataValue(new Variant(instance)) } }; session.Write(null, writeCollection, out StatusCodeCollection results, out _); return results[0]; } // 业务调用示例 WriteComplexTypeNode(session, new NodeId("ns=2;s=Demo.WorkOrder"), (inst) => { Type instType = inst.GetType(); instType.GetProperty("WorkOrderName").SetValue(inst, "生产工单-001"); instType.GetProperty("CreateTime").SetValue(inst, DateTime.Now); // 嵌套复杂类型、枚举、数组类型的属性,按对应类型创建实例赋值即可,不需要额外处理序列化 });
注意事项
- 动态生成的
RuntimeType是运行时通过Emit生成的,编译时不存在强类型定义,不要尝试直接用new关键字实例化,必须通过反射创建和操作属性。 - 包含枚举、数组、嵌套复杂类型的场景不需要额外处理编码逻辑,官方库会自动按照OPC UA规范完成序列化,只需要给对应属性赋值符合类型要求的值即可。
- 类型缓存是全局的,只要会话不重建、服务端类型定义不更新,缓存可以一直复用,不需要每次写入都重新读取元数据或者加载类型。
内容的提问来源于stack exchange,提问作者user3696412
相关产品推荐
相关产品推荐

