公司动态
Jot源码解析:.NET表达式树如何动态生成属性Getter与Setter
Jot源码解析.NET表达式树如何动态生成属性Getter与Setter【免费下载链接】JotJot is a library for persisting and applying .NET application state.项目地址: https://gitcode.com/gh_mirrors/jot1/JotJot 是一款专注于 .NET 应用状态持久化的轻量级开源库只需几行配置就能让窗口尺寸、控件位置、用户偏好等属性自动完成保存与恢复。这篇 Jot 源码解析文章将带你深入核心实现揭秘它如何借助 .NET 表达式树Expression Tree和Expression.Compile动态生成属性 Getter 与 Setter 委托实现比反射更优雅、更高效的属性读写。无论你是刚接触表达式树的初学者还是想借鉴开源设计思路的进阶开发者这篇文章都能让你快速看懂这套表达式即代码的精妙玩法。Jot 是什么一分钟理解 .NET 状态持久化库几乎所有桌面应用都要维护自身状态窗体大小、窗口位置、上次输入的账号、选中的标签页……传统做法是用.settings文件逐条读写一个窗口的属性要在五个地方重复出现繁琐且易错。Jot 把这件事抽象成了三个动作动作含义对应源码声明告诉 Jot 要跟踪哪些对象的哪些属性Tracker.ConfigureT()应用启动时把已保存的数据写回对象Tracker.Track(obj)持久化触发事件时把属性值存入存储Persist/PersistOn其中的灵魂就是通过表达式树为每个被跟踪属性动态生成 Getter 与 Setter这也是本文的核心。属性追踪的基石TrackedPropertyInfo 委托容器在深入表达式树之前先认识存放读与写能力的容器。Jot 把每个被跟踪属性的读写能力封装成两个委托Funcobject, object Getter读取属性值Actionobject, object Setter写入属性值它们被统一塞进TrackedPropertyInfo对象存放在配置的TrackedProperties字典里。源码位于Jot/Configuration/TrackedPropertyInfo.csGet 与 Set 全部以委托形式暴露这意味着 Jot 在应用数据时完全不关心属性背后是普通属性还是嵌套对象只管调用委托即可。表达式树生成 Getter一行 Compile 搞定Getter 的生成是整个流程里最简洁的一步。看Jot/Configuration/TrackingConfiguration.cs中Property方法的核心代码var getter propertyAccessExpression.Compile();就这么一行用户传入的ExpressionFuncT, TProperty例如w w.Width本身就是一棵表达式树调用Compile()后编译器会把表达式树转成可执行的 IL 代码并返回一个强类型委托这个委托就等价于手写的w w.Width。✅ 你看到的这行代码就是表达式树动态生成属性 Getter的全部秘密。表达式树生成 SetterExpression.Assign 赋值麻烦的是 Setter因为 C# 的 lambda 表达式不能直接写赋值语句w w.Width 100是不合法的。Jot 的解法是手动构造一棵赋值表达式树var right Expression.Parameter(typeof(object)); var setter Expression.Lambda( Expression.Block( Expression.Assign(membershipExpression, Expression.Convert(right, membershipExpression.Type)), Expression.Empty()), propertyAccessExpression.Parameters[0], right ).Compile() as ActionT, object;拆解这三个关键节点Expression.Assign构造把右侧值赋给左侧属性的赋值节点Expression.Convert由于外部传值是object需要先转换成属性的真实类型如intExpression.Lambda(...).Compile()把整棵表达式树编译成ActionT, object委托这样一个w w.Width的表达式就被翻译成了一个能写入w.Width的 Setter 委托。整段逻辑位于TrackingConfiguration.cs的Property方法约 L497-L519。 小知识因为赋值目标是表达式节点而非固定成员所以 Jot 天然支持嵌套属性比如w w.Font.SizeExpression.Assign会自动处理成员访问链。批量生成匿名类型投影的 Properties 方法单属性用Property多属性用Properties。看 README 中最经典的用法Tracker.ConfigureWindow() .Id(w w.Name) .Properties(w new { w.Height, w.Width, w.Left, w.Top, w.WindowState }) .PersistOn(nameof(Window.Closed));Properties方法接收的是ExpressionFuncT, object匿名类型投影表达式其内部实现TrackingConfiguration.csL527-L561做了三件事从表达式树中取出NewExpression拿到每个属性的成员元数据对每个成员循环调用与Property相同的套路生成 Getter 与 Setter把结果批量写入TrackedProperties字典它还做了个贴心的兼容处理VB.NET 编译器会把匿名类型投影包装成一层Convert节点Jot 通过UnaryExpression判断并剥离这层包装保证 VB 项目也能正常使用。从委托到数据流转Getter 与 Setter 如何驱动持久化生成的委托最终在Tracker的两个生命周期动作里发挥作用恢复状态Apply——Tracker.Track(obj)时调用object value data[propertyName]; // 从存储取回旧值 descriptor.Setter(target, value); // 调用动态生成的 Setter 写回属性保存状态Persist——触发事件时调用var value TrackedProperties[propertyName].Getter(target); // 调用动态生成的 Getter 读出属性 Tracker.Store.SetData(name, values); // 存入 IStoreSetter 在写入前还会经过Convert做类型转换处理枚举、Int64写Int32等情况并在WhenApplyingProperty/WhenPersistingProperty回调中支持拦截与取消灵活性相当高。数据最终由Jot/Storage/JsonFileStore.cs序列化为 JSON 文件保存到用户目录。为什么选表达式树而不是反射这是源码里一个很有意思的进化痕迹。在TrackingConfiguration.cs的属性读取逻辑中基于[Trackable]特性的路径仍在使用pi.GetValue(x)这类反射调用代码里还留着一句 TODO// todo: use Expression API to generate getters/setters instead of reflection而面向开发者的流式 API 则早已全面转向表达式树。两者对比对比维度反射调用表达式树 Compile性能每次调用都有反射开销一次性编译之后近乎原生调用类型安全弱类型运行期才报错编译期校验智能提示可读性元数据字符串满天飞强类型 lambda意图清晰正因如此Jot 把写代码变成了描述代码用户用 lambda 描述属性框架用表达式树把它编译成可执行委托兼顾了开发体验与运行性能。总结从 Jot 源码中学到什么回看整个设计Jot 用 5 个文件就讲清了表达式树的核心价值Jot/Configuration/TrackingConfiguration.cs—— 表达式树生成 Getter/Setter 的主战场Jot/Configuration/TrackedPropertyInfo.cs—— 委托容器定义Jot/Configuration/TrackingConfigurationGeneric.cs—— 强类型 API 转发层Jot/Tracker.cs—— 生命周期调度与弱引用管理Jot/Storage/JsonFileStore.cs—— JSON 持久化落地你可以把表达式树动态生成属性访问器这套技巧直接迁移到自己的工具库中例如打造一个通用读写器、ORM 映射器或配置系统。如果你对 Jot 的完整实现感兴趣可以 clone 仓库到本地git clone https://gitcode.com/gh_mirrors/jot1/Jot配合Jot.Tests/TrackerTests.cs中的测试用例一起阅读理解效率会翻倍。【免费下载链接】JotJot is a library for persisting and applying .NET application state.项目地址: https://gitcode.com/gh_mirrors/jot1/Jot创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考