如何在DTO类中自动实现持久化、克隆及XML序列化功能?
我完全懂你的痛点——重复写这些DTO的基础逻辑不仅无聊透顶,还容易因为手滑搞出各种低级bug。既然反射(性能顾虑)和常规代码生成(两次构建太麻烦)都不合心意,我给你几个更顺手的解决方案:
1. 使用C#源生成器(Source Generators)
这绝对是现在.NET生态里的最优解之一,完美解决你提到的两次构建问题——源生成器是在编译过程中直接运行的,基于你的DTO源代码生成逻辑,和项目一起编译,完全不需要额外的构建步骤。
实现思路:
- 先定义一个自定义标记属性,比如
[XmlDto],用来标记需要自动生成逻辑的DTO类; - 写一个源生成器,扫描项目中带有这个属性的类,分析它的公共属性(类型、名称等);
- 自动生成
LoadFromXml(XmlNode node)、SaveToXml(XmlNode node)和Clone()方法的原生代码,直接嵌入到项目编译流程里。
核心优势:
- 性能拉满:生成的代码和你手写的完全一样,运行时没有任何反射开销;
- 零额外步骤:编译时自动完成,不需要手动触发二次构建;
- 高度可定制:可以针对不同属性类型(值类型、枚举、引用类型等)编写专属生成逻辑,比如处理空值、格式转换等。
简单的生成逻辑示例(伪代码):
// 源生成器里的核心代码片段 foreach (var property in targetDtoClass.Properties) { // 生成SaveToXml的代码 generatedCode.AppendLine($"node.SetAttributeValue(\"{property.Name}\", this.{property.Name});"); // 生成LoadFromXml的代码 generatedCode.AppendLine($"this.{property.Name} = ({property.Type})Convert.ChangeType(node.Attributes[\"{property.Name}\"]?.Value, typeof({property.Type}));"); // 生成Clone的代码 generatedCode.AppendLine($"newDto.{property.Name} = this.{property.Name};"); }
2. 接口+默认实现+表达式树缓存(C# 8.0+)
如果你不想折腾生成器这类工具,用C#的接口默认实现结合表达式树缓存,也能平衡复用性和性能——既避免重复写代码,又解决了纯反射的性能问题。
实现思路:
- 定义一个接口
IXmlSerializableDto,包含你需要的三个方法; - 在接口的默认实现里,用表达式树提前编译每个属性的Getter/Setter,缓存到静态字典里;
- 所有DTO只要实现这个接口,就能自动获得这些方法,不用重复编写。
示例代码片段:
public interface IXmlSerializableDto { void LoadFromXml(XmlNode node) { var dtoType = GetType(); // 静态字典缓存属性Setter的编译表达式 if (!PropertySetterCache.TryGetValue(dtoType, out var setters)) { setters = dtoType.GetProperties() .ToDictionary(p => p.Name, BuildSetterExpression); PropertySetterCache[dtoType] = setters; } foreach (XmlAttribute attr in node.Attributes) { if (setters.TryGetValue(attr.Name, out var setter)) { var value = Convert.ChangeType(attr.Value, setter.PropertyType); setter(this, value); } } } // SaveToXml和Clone的默认实现逻辑类似,用表达式树缓存Getter void SaveToXml(XmlNode node); IXmlSerializableDto Clone(); // 静态缓存容器 static Dictionary<Type, Dictionary<string, Action<object, object>>> PropertySetterCache { get; } = new(); // 构建属性Setter的表达式树并编译 static Action<object, object> BuildSetterExpression(PropertyInfo prop) { var instanceParam = Expression.Parameter(typeof(object)); var valueParam = Expression.Parameter(typeof(object)); var castInstance = Expression.Convert(instanceParam, prop.DeclaringType); var castValue = Expression.Convert(valueParam, prop.PropertyType); var setter = Expression.Assign(Expression.Property(castInstance, prop), castValue); return Expression.Lambda<Action<object, object>>(setter, instanceParam, valueParam).Compile(); } }
核心优势:
- 零额外工具依赖:纯C#语言特性就能实现;
- 性能接近原生:第一次调用会编译表达式树,后续调用直接执行编译后的IL代码,比反射快几个数量级;
- 侵入性低:DTO只要实现接口就行,不需要修改其他逻辑。
3. T4模板(Text Template Transformation Toolkit)
如果源生成器对你来说有点门槛,T4模板也是个靠谱的选择——它能在设计时直接生成代码,你可以实时看到生成的文件,调试起来也方便,同样不需要二次构建。
实现思路:
- 创建一个
.tt模板文件,编写逻辑扫描你的DTO所在的命名空间; - 用Roslyn API分析DTO的源代码(或者直接引用编译后的程序集,更简单),获取属性信息;
- 模板自动生成每个DTO的目标方法代码,保存后直接参与项目编译。
核心优势:
- 可视化生成:生成的代码直接出现在项目里,你可以随时查看、修改(当然不建议手动改生成的代码);
- 性能无损耗:生成的是原生代码,和手写逻辑完全一致;
- 上手简单:语法接近常规C#,不需要深入理解编译器原理。
总结一下选择建议:
- 追求现代.NET体验和极致性能:选源生成器;
- 不想折腾工具,追求快速落地:选接口+表达式树缓存;
- 喜欢能直观看到生成代码的方式:选T4模板。
内容的提问来源于stack exchange,提问作者codymanix
相关产品推荐
相关产品推荐

