C#对象动态添加属性:ExpandoObject、DynamicObject与Emit实战 如果你写过上位机或者数据采集类的系统一定遇到过这种让人抓狂的情况业务对象早就定义好了结果对接的设备或者外部服务今天要多传一个温度明天要多带一个通道名称后天又要加一个检测结果。前端接口已经定死数据模型又不能总是为了某个临时场景去改类、加属性、重新编译发布。于是很多人会问一句话能不能在运行时给现有对象动态添加属性这个问题在 C# 里不像在 JavaScript 里那么直接。JS 的对象本来就是哈希表的形态你随时可以obj.extra 1。但 C# 是强类型语言对象的结构在编译期基本就锁死了。最近我在一个 .NET 9 项目里专门折腾了这整件事把 ExpandoObject、DynamicObject、反射 Emit、序列化扩展这些方案都试了一遍也踩了几个比较隐蔽的坑。这篇就把不同做法的适用场景、完整代码、以及实测下来的注意点整理出来。如果你正准备在 C# 上位机、机器视觉、或者报表字段动态拼接这类项目里加动态属性这篇应该能帮你少绕很多弯路。1. 先把需求拆明白到底要“动态”到哪一层很多朋友一上来就问“怎么给对象动态加属性”但经过我仔细观察他们真正想要的其实是完全不同层面的东西。这步没理清楚后面所有方案都会选错。1.1 三个不同层级的动态化需求第一层我希望原对象的实例本身长出新的属性。比如我已经有一个DeviceData对象在内存里跑着我希望直接对实例执行类似device.Temperature 26.5的操作。注意这句话逻辑上有个硬伤类的结构在编译期已经固定类型系统不允许它平白无故多出一个Temperature属性。想让实例长出属性本质上是需要让编译器在运行时看到一个“新类型”这条路只能走反射生成子类型或者动态代理代价比较高。第二层我能接受新建一个对象这个对象包含原有对象的所有字段同时还能附加新字段。这种场景最常见。比如从设备采集到基础数据后我要输出一个带检测结果的完整报文或者在 WinForm 表格里临时拼几个展示列。原始对象可以保持不变我只需要一个“看起来像原来对象、但又多了字段”的新对象。这种做法工程量小使用也灵活。第三层其实我只需要输出 JSON 时多几个字段。说白了系统对接是 JSON 报文层面的事内部我根本不关心那个类是不是真的有这些属性我只要序列化之后键值对齐全就行。这种情况最简单甚至可以完全不动业务对象只做序列化拼装。我在实际项目里遇到最多的其实是第二层和第三层真正需要第一层的反而是极少数。比如 C# 上位机开发中通讯报文结构经常跟着设备型号走但同一套界面控件、同一个处理流程还想复用动态给对象挂字段就是常用技巧。1.2 .NET 9 和 C# 13 到底带来了什么新底牌先给个结论.NET 9 并没有提供一个类似 Python 那种“运行时随便给对象加属性”的原生 API。C# 语言本身从 C# 4 开始有dynamic关键字配合ExpandoObject和DynamicObject可以做到动态成员访问但底层依然是字典或者自定义动态分发。不过 .NET 9 时代的几个趋势对这个问题有实际影响C# 12 / .NET 8 之后主构造函数、集合表达式等语法让写这类辅助代码省了不少样板。.NET 9 进一步强化了 Native AOT 支持而Reflection.Emit不能在 Native AOT 环境下使用所以单纯用老反射方案的同学会越来越受限。C# 13 引入了“扩展成员Extension Members”语言预览特性它能在编译期给已有类型追加属性和方法算是部分回应了“想让现有类多点东西”的诉求。但它是静态扩展和“运行时动态添加”有本质区别。所以现在的选型不能只看“能不能实现”还要看你的部署环境允不允许运行时生成 IL。这块我在后面方案对比里会重点说。2. 入门最常用方案ExpandoObject 与 dynamic 组合如果你的动态字段不要求绑定到某个具体业务类型或者你的场景本来就是“整个对象都是动态的”那 ExpandoObject 绝对是第一个要试的方案。2.1 ExpandoObject 三分钟上手提到 C# 动态属性很多人第一个想到的就是它。使用方式是这样的using System.Dynamic; dynamic device new ExpandoObject(); device.Name 一号采集站; device.ChannelCount 8; device.Status Online;赋值之后你可以直接通过device.Name读取。但这里有一个太多新手踩过的坑如果你不是用dynamic声明变量而是写成下面这样编译都会不过var device new ExpandoObject(); device.Name 一号采集站; // 编译错误ExpandoObject 没有 Name 属性因为var推断出来的类型是ExpandoObject而 ExpandoObject 编译期并没有Name属性。它之所以能用点号访问靠的是dynamic在运行时动态绑定。一旦声明成具体类型编译器就不会走这套逻辑。ExpandoObject 本质上就是IDictionarystring, object?的一层外壳。这一点非常有用很多时候你需要遍历它所有的动态属性直接把它转成字典再处理dynamic device new ExpandoObject(); device.Name 一号采集站; device.ChannelCount 8; var map (IDictionarystring, object?)device; foreach (var kv in map) { Console.WriteLine(${kv.Key} {kv.Value}); }输出结果很直观所有动态加进去的字段都会出现在字典里。2.2 把现有对象的属性“搬”进动态对象ExpandoObject 还有一个很常见的用法把一个已经存在的普通对象的属性全部复制到 ExpandoObject 上然后再追加新字段。相当于做了个“扩展投影”。比如我现在有个温度设备实体public class TemperatureDevice { public string DeviceId { get; set; } ; public double Temperature { get; set; } }我需要生成一份包含设备基础信息外加人工判定结果的报文就可以这样写public static ExpandoObject ToExpando(object source) { var expando new ExpandoObject(); var dict (IDictionarystring, object?)expando; foreach (var property in source.GetType().GetProperties()) { if (property.GetIndexParameters().Length 0) { continue; } dict[property.Name] property.GetValue(source); } return expando; }调用起来效果相当漂亮var device new TemperatureDevice { DeviceId DEV-001, Temperature 36.8 }; dynamic payload ToExpando(device); payload.JudgeResult OK; payload.AlarmLevel 1; var payloadDict (IDictionarystring, object?)payload; Console.WriteLine(payloadDict[DeviceId]); // DEV-001 Console.WriteLine(payloadDict[JudgeResult]); // OK这种“反射读取属性 ExpandoObject 承载额外字段”的组合非常适合先把原对象丢给第三方组件第三方再往上面追加自定义字段的场景。注意这里面有个概念必须想清楚原来的device对象本身并没有真的多出属性我们只是创建了一个新的动态对象把旧对象的值复制进去并追加了字段。原实例的类型自始至终都没有改变。ExpandoObject 的方案优缺点也非常明显。轻量、代码好写、遍历方便是它的最大优势。但它如果放在数据量很大的场景下性能就一般了因为每次动态访问都要经过字典查找。而且如果你做了属性名拼写错误编译器不会告诉你直到运行时才会抛出RuntimeBinderException。做上位机长期维护时这种“运行到某行才炸”的写法要格外小心。3. 想“包装”现有对象又保留原始字段的自定义 DynamicObject 方案ExpandoObject 有一个现实问题它复制字段时是“快照式”的。如果原对象的属性值变了ExpandoObject 里拷贝出来的那份不会同步更新。在部分上位机界面绑定、实时数据监控场景里这会导致界面数据和实际数据对不上。如果你希望在不破坏原类型的情况下给“现有对象实例”接入动态字段同时保留原对象属性的实时读取能力可以考虑自定义一个DynamicObject包装器。3.1 自定义动态包装器实现思路不复杂创建一个泛型包装类内部持有原对象引用同时维护一个字典专门存放动态追加的属性。当动态访问发生时先查字典查不到就反射去读原对象的真实属性。这个包装器的代码我贴出来可以直接抄using System.Dynamic; using System.Reflection; public class ExtensionProxyT : DynamicObject where T : class { private readonly T _target; private readonly Dictionarystring, object? _extras new(StringComparer.OrdinalIgnoreCase); private readonly Dictionarystring, PropertyInfo? _propertyCache; public ExtensionProxy(T target) { _target target ?? throw new ArgumentNullException(nameof(target)); _propertyCache new Dictionarystring, PropertyInfo?(StringComparer.OrdinalIgnoreCase); foreach (var prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { _propertyCache[prop.Name] prop; } } public T Target _target; public override bool TryGetMember(GetMemberBinder binder, out object? result) { if (_propertyCache.TryGetValue(binder.Name, out var realProp) realProp is not null realProp.CanRead) { result realProp.GetValue(_target); return true; } if (_extras.TryGetValue(binder.Name, out result)) { return true; } result null; return false; } public override bool TrySetMember(SetMemberBinder binder, object? value) { if (_propertyCache.TryGetValue(binder.Name, out var realProp) realProp is not null realProp.CanWrite) { realProp.SetValue(_target, value); return true; } _extras[binder.Name] value; return true; } public override IEnumerablestring GetDynamicMemberNames() { foreach (var name in _propertyCache.Keys) { yield return name; } foreach (var name in _extras.Keys) { yield return name; } } }调用的时候需要用dynamic来接收包装对象public class ConnectionConfig { public string Host { get; set; } 127.0.0.1; public int Port { get; set; } 502; } var config new ConnectionConfig { Host 192.168.1.10, Port 502 }; dynamic proxy new ExtensionProxyConnectionConfig(config); // 读取原始属性走反射 Console.WriteLine(proxy.Host); // 添加动态属性走 extras 字典 proxy.ChannelName 流水线A; proxy.TimeoutMs 3000; // 动态属性可以正常读取 Console.WriteLine(proxy.ChannelName); // 原始对象属性变化包装器依旧读得到最新值 config.Port 503; Console.WriteLine(proxy.Port); // 503注意代码中我对原有的可写属性做了特殊处理如果访问名能对应到原对象的真实属性并且属性可写就优先写入原属性保持数据双向一致如果原属性是只读的或者根本没有这个属性才存进_extras动态字典。这个设计在多半业务场景下最好用。3.2 理解 DynamicObject 的绑定机制DynamicObject可以理解为C# 编译器遇到dynamic对象上的成员访问时不直接生成普通的成员调用指令而是生成一段“调用动态绑定逻辑”的代码。绑定逻辑会调用你重写的方法比如TryGetMember、TrySetMember、TryInvokeMember。这个方法设计得非常有意思它让你可以把“动态属性”完全掌控在自己手里。比如你可以让属性名忽略大小写或者根据权限决定一个属性到底能不能被赋值。我就曾经在上位机项目里用这个方式做了设备参数的“只读/可写”控制某些参数只允许上位机读不允许现场手动改在TrySetMember里直接拒绝即可。还需要注意一个坑DynamicObject的子类只有在被当作dynamic使用时才会走动态绑定。如果你把它改成具体类型比如var proxy new ExtensionProxyConnectionConfig(config); proxy.Host;编译器会告诉你ExtensionProxyConnectionConfig上面没有Host属性。这跟 ExpandoObject 的情况一模一样动态分派只发生在动态调用点上。另外GetDynamicMemberNames建议尽量重写。很多调试工具、数据序列化组件、以及某些 UI 绑定框架都会通过这个方法来了解对象有哪些动态成员。不重写的话外部工具可能只能看到类上真正的静态成员完全不知道你挂了额外的动态字段。4. 不碰对象本身序列化层的“动态添加属性”在实际做系统对接的时候有很大一部分场景根本不需要让内存里的对象真正多一个属性你只是希望最终吐给别人的 JSON 里多几个字段。这种情况下最不容易出问题的方案其实是直接在序列化层做。4.1 用字典合并动态字段思路非常简单把原对象序列化或者反射成一个字典结构然后把动态附加字段放进去。我比较推荐的做法是写一个通用扩展方法public static class ObjectExtensions { public static Dictionarystring, object? AttachExtra( this object source, params (string Name, object? Value)[] extras) { var result new Dictionarystring, object?(StringComparer.OrdinalIgnoreCase); foreach (var property in source.GetType() .GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)) { if (property.GetIndexParameters().Length 0) { result[property.Name] property.GetValue(source); } } foreach (var extra in extras) { result[extra.Name] extra.Value; } return result; } }调用起来很舒服var device new TemperatureDevice { DeviceId DEV-002, Temperature 42.1 }; var payload device.AttachExtra( (JudgeResult, OverTemp), (Score, 0.98) ); Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(payload));这里完全没有修改device对象也没有新建什么动态类型只是在最终输出的载荷层做了一次合并。如果团队约定好对外报文统一用字典生成那么这个方案最干净既不会污染业务模型也不影响内部强类型的使用。唯一要留意的是属性名如果和附加字段重名会把原来的值覆盖掉所以在附加字段的命名上要养成带前缀的好习惯比如加ext_或者按业务模块命名。4.2 直接操作 JsonObject 再序列化如果你的项目已经基于 System.Text.Json还可以用JsonObject直接对序列化得到的节点做追加字段。它的好处是不用关心原对象具体有哪些属性框架自动把属性变成了 JSON 节点你再往里塞新字段就行using System.Text.Json; using System.Text.Json.Nodes; var device new TemperatureDevice { DeviceId DEV-003, Temperature 39.5 }; JsonObject json JsonSerializer.SerializeToNode(device)!.AsObject(); json[DetectionTime] JsonValue.Create(DateTime.Now); json[JudgeResult] JsonValue.Create(Fever); Console.WriteLine(json.ToJsonString(new JsonSerializerOptions { WriteIndented true }));这种方式常用于网关服务或者协议转换层。比如你做机器视觉系统的时候海康相机或者 VisionMaster 跑完检测会产生结果数据你不想把视觉结果字段写死在设备实体类里就可以先把实体序列化成JsonObject再把识别标签、置信度、检测框这些动态信息追加进去。JsonObject方式也有边界如果一个 JSON 值本身是嵌套对象你要用JsonObject去组装比直接反射字典要繁琐。而且JsonValue.Create对null值的处理不是那么无脑字段值经常为空时直接用字典方案会更省心。4.3 为什么我不建议无脑上全局转换器可能有人已经想到了能不能写一个自定义JsonConverterFactory让所有对象在序列化的时候都自动带上外部字典里的附加字段理论上当然可以但我在实际项目中试过之后发现这种“全局横切”的做法维护成本很高。因为你很难确定哪个类型需要扩展、哪个类型不需要扩展你也很容易和已有的自定义转换器互相打架。而且一旦约定混乱序列化出来的字段顺序、命名策略都会变成一个小型灾难。我的建议是把序列化层扩展限定在“出入口”要么在入站反序列化时把未知字段收集到JsonExtensionData字典要么在出站时显式调用上面的AttachExtra或JsonObject追加。让代码的读者一眼就能看出“这个对象被附加了什么”比在框架层做全局黑魔法要靠谱得多。5. 反射 Reflection.Emit运行时生成真正带新属性的类型如果你确实需要让一个对象实例在运行时拿出一个“新类型”也就是第一层那类需求可以考虑Reflection.Emit。它能动态创建一个继承自原类型的子类给子类增加新属性然后把原对象的数据复制到新对象上。最终你在外面拿到的对象类型已经不是原来的TemperatureDevice而是TemperatureDevice_Ext但它可以赋给TemperatureDevice变量并且通过反射能拿到新增的JudgeResult属性。5.1 思路拆解为什么是子类而不是改原类CLR 不允许运行时修改一个已经加载的类型的成员列表。想给现有类型加属性唯一可靠的方向是创建一个子类型把原类型已有的字段和方法都继承下来再在子类型上声明新的属性。因为子类自然继承原类的公共成员所以把子类实例赋给基类引用在类型系统上是完全合法的。Reflection.Emit的好处是能做真正有PropertyInfo的属性System.Text.Json、数据表格绑定、表达式树等看得到 CLR 元数据的组件都能识别它。坏处是代码复杂度高而且只有在未被 Native AOT 裁剪的运行时环境才可用。5.2 核心代码动态生成子类型并添加属性下面这段代码我做了精简留下最核心的动态类型生成逻辑。它能把一个新属性挂到某个类型的动态子类上using System.Reflection; using System.Reflection.Emit; public static class DynamicTypeBuilder { public static Type ExtendWithProperty( Type baseType, string propertyName, Type propertyType) { AssemblyBuilder assembly AssemblyBuilder.DefineDynamicAssembly( new AssemblyName(DynamicAssembly), AssemblyBuilderAccess.Run); ModuleBuilder module assembly.DefineDynamicModule(MainModule); TypeBuilder typeBuilder module.DefineType( baseType.Name _Ext, TypeAttributes.Public | TypeAttributes.Class, baseType); string fieldName _ char.ToLowerInvariant(propertyName[0]) propertyName.Substring(1); FieldBuilder field typeBuilder.DefineField( fieldName, propertyType, FieldAttributes.Private); PropertyBuilder property typeBuilder.DefineProperty( propertyName, PropertyAttributes.None, propertyType, Type.EmptyTypes); MethodBuilder getter typeBuilder.DefineMethod( get_ propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes); ILGenerator getIl getter.GetILGenerator(); getIl.Emit(OpCodes.Ldarg_0); getIl.Emit(OpCodes.Ldfld, field); getIl.Emit(OpCodes.Ret); property.SetGetMethod(getter); MethodBuilder setter typeBuilder.DefineMethod( set_ propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(void), new[] { propertyType }); ILGenerator setIl setter.GetILGenerator(); setIl.Emit(OpCodes.Ldarg_0); setIl.Emit(OpCodes.Ldarg_1); setIl.Emit(OpCodes.Stfld, field); setIl.Emit(OpCodes.Ret); property.SetSetMethod(setter); return typeBuilder.CreateTypeInfo().AsType(); } }然后写一个通用的“复制并添加”方法把原对象的公共属性值拷贝到新对象上public static TBase AddPropertyToInstanceTBase( TBase source, string propertyName, object? propertyValue) where TBase : class { ArgumentNullException.ThrowIfNull(source); Type dynamicType DynamicTypeBuilder.ExtendWithProperty( source.GetType(), propertyName, propertyValue?.GetType() ?? typeof(object)); TBase instance (TBase)Activator.CreateInstance(dynamicType)!; foreach (PropertyInfo prop in source.GetType().GetProperties( BindingFlags.Public | BindingFlags.Instance)) { if (prop.CanRead prop.CanWrite prop.GetIndexParameters().Length 0) { prop.SetValue(instance, prop.GetValue(source)); } } dynamicType.GetProperty(propertyName)!.SetValue(instance, propertyValue); return instance; }使用示例var source new TemperatureDevice { DeviceId DEV-004, Temperature 38.2 }; TemperatureDevice extended AddPropertyToInstance( source, JudgeResult, OverTemp); Type t extended.GetType(); Console.WriteLine(t.FullName); // TemperatureDevice_Ext Console.WriteLine(t.GetProperty(JudgeResult)?.GetValue(extended)); // OverTemp这段代码最核心的价值在于extended变量的静态类型仍然是TemperatureDevice你原有代码里所有接收基类的方法都可以继续用但通过反射你在运行时拿到了一个真正的新属性。5.3 使用 Emit 的几个重要提醒首先Activator.CreateInstance(dynamicType)要求原类型必须具备可访问的无参构造函数。如果原类型只有带参构造函数你就需要用ConstructorBuilder显式定义构造函数或者通过FormatterServices.GetUninitializedObject跳过构造函数创建对象后者危险性高不那么建议轻易使用。其次复制属性时如果原类型里有只读属性或者没有 setter 的属性字段值不会自动复制过去。比如某些模型类通过构造函数初始化属性而没有 setter你复制出来的子类实例很可能属性值为默认空值。这种问题很难在运行前发现要在封装层做防御性检查和日志。最后是 AOT 兼容性问题。如果你的 .NET 9 项目开启了 Native AOT 发布运行时不能再通过Reflection.Emit动态创建类型。这是硬限制。所以在工业上位机场景里如果你打算做离线部署又追求启动快我建议少用 Emit尽量往 ExpandoObject、DynamicObject 或序列化扩展方案上靠。6. 认识 C# 13 的扩展成员它和“动态”是两回事既然标题里挂着 .NET 9就不得不提 C# 13 带来的一个语言级新方向扩展成员。这个特性虽然还在预览阶段但它确实能给你一种“给既有类型加成员”的能力。6.1 扩展成员的意图与限制C# 从很早就有扩展方法但它只能加方法不能加属性。C# 13 预览版把扩展能力推进了一大步你可以为已有类型声明扩展属性、扩展静态成员甚至允许扩展字段的占位接口。语法上更接近为类型追加一段“成员块”让你在没有继承、没有修改源码的情况下让某个类型从调用方视角看起来拥有新成员。不过要特别强调扩展成员是编译期绑定不是运行时动态加属性。所谓“扩展出来的属性”本质上是编译器在后台帮你生成一段访问逻辑属性集合在编译完成后就固定了。它不会因为外部输入不同而在进程运行期间改变成员集合。你无法一边跑着程序一边往扩展类里面塞一个运行时才决定名字的属性。那它有什么用如果你是自己在维护一套基础库经常需要给第三方模型补充特定领域的计算字段又不希望业务代码到处用字符串字典那扩展成员是很好的“静态层面的补丁”。使用的时候需要在工程文件里开启预览语言版本PropertyGroup LangVersionpreview/LangVersion /PropertyGroup由于是预览特性API 和写法在后续版本里还有调整的可能生产项目如果追求稳定建议先做小规模验证再决定是否铺开。6.2 和 DynamicObject 的分工可以把 C# 13 扩展成员理解成“编译期锦上添花”DynamicObject / ExpandoObject 则是“运行时动态应变”。两者不是竞争关系而是服务于不同节奏的需求。如果你的字段是从配置、数据库或者上位机采集报文里读出来的字段数量甚至字段名都要看现场情况那扩展成员救不了你因为没人能在编译期把这些未知字段写进去这时候还是动态对象或者字典承载比较合适。如果字段是固定的只是你不想污染实体类、不想为了几个展示字段大动干戈那扩展成员将来很可能比 DynamicObject 更值得依赖——它有编译器检查重构时不容易把字符串写错。但我在这篇里没有把扩展成员作为主推方案原因很简单发布稳定版本的项目里少用还在预览期的语言特性永远是安全第一的原则。你在 GitHub 上玩样例可以拿到产线上跑还是先把 ExpandoObject 或 JsonObject 跑通再说。6.3 选择方案前的优先级判断我自己在项目里判断优先级是从“最小副作用”入手。第一优先级永远是序列化层拼字段。如果只是报文输出或者接口返回那毫不客气地说给内存对象搞动态属性属于过度设计用AttachExtra或JsonObject就够了。第二优先级才是 ExpandoObject 和 DynamicObject。它们适合业务逻辑里确实需要以属性方式访问动态字段的场景比如 UI 绑定、动态条件处理、协议解析后的统一访问。第三优先级才轮到 Reflection.Emit。它适合强类型组件要求你提供一个真正带新属性的 Type 的场景例如某些表格控件的高级绑定、某些表达式树计算以及旧框架对PropertyDescriptor有依赖的场景。你要为它付出更多的调试成本同时必须放弃 Native AOT 发布这条路。7. 选型速查与动态属性排错经验整篇谈了很多方案最后用一张表帮你迅速定位。表格不是万能的但至少能把大体方向固定下来避免在错误方案里越走越深。场景推荐方案理由主要代价接口或 JSON 输出要额外字段字典合并 / JsonObject不动业务模型代码直观需要约定字段命名原对象字段要动态展示且字段随时变ExpandoObject最简单遍历方便无法和原对象属性保持双向同步要包装现有对象并保持原属性实时一致DynamicObject能同时管理真实属性和动态

相关新闻

最新新闻

BUUCTF Misc第16-20题实战:隐写分析、二维码修复与AES解密全解析

BUUCTF Misc第16-20题实战:隐写分析、二维码修复与AES解密全解析

刷BUUCTF Misc的题,很多人都是从第1题开始一路往后啃的,我最近正好啃到题单里的第16到20题。这一批题画风很杂:有签到题里藏着看不见的字符,有二维码图片扫不出来,有从一百张图里挑一个有问题的,还有一个名…

2026/9/9 6:06:23
AI率检测原理与降AI工具实测:从困惑度到双平台差异

AI率检测原理与降AI工具实测:从困惑度到双平台差异

这个月我已经第三次看到有人在同一句话里崩溃:“我自己一个字一个字敲出来的文章,AI率怎么还73%?”说实话,我最初对“降AI率工具”这个词是有一点抵触的,总觉得它带着某种不太好上台面的目的。直到自己一篇完全没用AI生…

2026/9/9 6:06:23
数据安全与API安全双赛道发力,解读2026年网安全景图入选价值

数据安全与API安全双赛道发力,解读2026年网安全景图入选价值

1. 全景图入选背后:一份行业“地图”的价值在哪 先说个我自己的体会。做网络与信息安全这行,最怕的不是技术难题,而是看不清自己在行业里的坐标。技术方向那么多,数据安全、应用安全、零信任、攻防演练,每一条赛道都有…

2026/9/9 6:06:23
DINOv2自监督视觉预训练:从特征提取到微调部署的完整指南

DINOv2自监督视觉预训练:从特征提取到微调部署的完整指南

简介:面向深度学习研究与开发者的Dinov2自监督视觉模型完整代码与预训练权重包,基于Transformer架构,专注解决无标注数据下的视觉表示学习及下游任务微调问题。资源共90个文件,包含58个Python源码、10个YAML配置文件、4个PTH预训练…

2026/9/9 6:06:23
逆向识别SHA哈希算法:从静态特征到版本鉴别技巧

逆向识别SHA哈希算法:从静态特征到版本鉴别技巧

前阵子分析一个 Android so 的校验逻辑,函数没符号,字符串表也被处理过,能追的线索有限。调用链走到后半段时,我发现目标代码在按固定块大小处理输入,桶里反复出现异或、循环右移和加法,最后往缓冲区里写了…

2026/9/9 6:06:23
欧姆龙CJ2M PLC标准化程序模板:伺服与气缸控制模块化设计

欧姆龙CJ2M PLC标准化程序模板:伺服与气缸控制模块化设计

做非标自动化这几年,我手里积攒最多的资料不是设备图纸,而是各种设备的PLC程序。每次接到新设备调试任务,最怕的就是打开一台控制伺服电机和气缸的设备,程序居然还是一个大梯形图从头铺到尾,改一个动作要在几十个程序段…

2026/9/9 6:01:23