如何解析字符串将{Object.Property}占位符通过反射替换为对应对象属性值
我想实现以下字符串转换效果:
- 将字符串中的
Account转换为<Account> - 将字符串中的
Object Name转换为<Object with matching name>
我现在需要实现一个字符串解析功能,把字符串里的占位符替换成对应对象的属性值,类似套用信函的逻辑,比如常见的「尊敬的[客户姓名],感谢您购买[商品名称]」这类模板。但现在模板里的字段不是我预先设置好的,所以需要找到方法把文本块里的占位符映射转换成对象属性值。
我目前的实现方案是在字符串里用{value=x; id=y ;property=z}格式的占位符,通过switch case语句转换为对应对象,再用对象属性值替换被{}包裹的占位符内容。
举个例子:输入字符串为"some random string {value=1; id=1; property=Name} continued random string"
我先解析字符串定位到{value=前缀,匹配成功后进入switch case逻辑,比如value为1时返回ID=1的Account实例,之后获取Account.Name属性值。
拿到对应值后我会展示在文本框里,让用户在生成最终待发送文档前先校验内容正确性。
现在我想优化实现方案,能不能直接在字符串里用{Object.Property}格式的占位符,通过反射把该字符串映射的属性值直接提取出来?
我之前用过通过反射从字符串获取属性值的方案,但这个方案要求我预先持有对应的对象实例,无法适配当前需求。
实现方案
完全可以实现你要的{Object.Property}格式的占位符效果,核心是通过全局对象注册容器解决预先持有实例的问题,再结合正则匹配+反射完成属性值提取,实现步骤如下:
1. 建立全局对象注册容器
你不需要预先持有所有实例,只要在渲染模板前,把所有可能用到的实例按别名注册到全局字典里即可,调用时按别名取对应实例:
// 全局对象注册容器,可根据需求改为单例模式实现 public static class TemplateObjectRegistry { private static readonly Dictionary<string, object> _instancePool = new(); // 注册实例:别名对应实例对象 public static void Register(string alias, object instance) { if (!string.IsNullOrEmpty(alias) && instance != null) _instancePool[alias] = instance; } // 按别名取实例 public static object GetInstance(string alias) { _instancePool.TryGetValue(alias, out var instance); return instance; } }
2. 实现模板渲染逻辑
用正则匹配所有{对象别名.属性名}格式的占位符,对每个匹配结果先从注册容器取实例,再用反射获取属性值:
using System.Text.RegularExpressions; public static class TemplateRenderer { // 匹配{xxx.xxx}格式的正则 private static readonly Regex _placeholderRegex = new Regex(@"\{(?<objAlias>[a-zA-Z0-9_]+)\.(?<propName>[a-zA-Z0-9_]+)\}", RegexOptions.Compiled); public static string Render(string template) { return _placeholderRegex.Replace(template, match => { var objAlias = match.Groups["objAlias"].Value; var propName = match.Groups["propName"].Value; // 1. 从容器获取实例 var targetObj = TemplateObjectRegistry.GetInstance(objAlias); if (targetObj == null) return match.Value; // 找不到实例返回原占位符,避免报错 // 2. 反射获取属性值 var propertyInfo = targetObj.GetType().GetProperty(propName); if (propertyInfo == null) return match.Value; // 找不到属性返回原占位符 var propValue = propertyInfo.GetValue(targetObj); return propValue?.ToString() ?? string.Empty; }); } }
3. 使用示例
// 模拟你的业务对象 public class Account { public int Id { get; set; } public string Name { get; set; } } public class Order { public int Id { get; set; } public string ProductName { get; set; } } // 调用逻辑 public static void Main() { // 你可以在任意时机注册需要用到的实例,不需要在写模板的时候就持有 var testAccount = new Account { Id = 1, Name = "张三" }; TemplateObjectRegistry.Register("Account", testAccount); var testOrder = new Order { Id = 123, ProductName = "XX智能手表" }; TemplateObjectRegistry.Register("Order", testOrder); // 渲染模板 string template = "尊敬的{Account.Name},感谢您购买{Order.ProductName}"; string result = TemplateRenderer.Render(template); // 输出结果:尊敬的张三,感谢您购买XX智能手表 Console.WriteLine(result); }
可选优化点
- 支持嵌套属性:如果需要用到
{Account.Contact.Phone}这类嵌套属性,只要把属性解析部分改成按.分割递归取值即可 - 加反射缓存:把每个类型的属性信息缓存到字典里,不用每次渲染都反射查类型,提升性能
- 扩展格式化支持:可以匹配
{Order.CreateTime:yyyy-MM-dd}这类带格式的占位符,拿到值后按指定格式输出
内容的提问来源于stack exchange,提问作者Robert Sullivan

