基于字符串键的变量设计:寻求更高效的算法或实现方案
优化字符串变量替换的性能方案(替代反射实现)
我正在设计一个支持设计师在编辑器中用{variableName}格式插入变量的文本区域,当前通过反射遍历类的FieldInfo获取值实现替换,但反射性能较差。需要找到功能、灵活性相当的更优实现方式。
原实现代码(可运行但效率低):
public static class FormattedString { public static string GetFormattedString(string baseString, object obj) { if (!baseString.Contains('{')) return baseString; string formattedString = ""; foreach (var split in baseString.Split('{')) { if (!split.Contains('}')) { formattedString += split; } else { int index = split.IndexOf('}'); formattedString += GetFieldValue(split.Substring(0, index), obj); if (index < split.Length) formattedString += split.Substring(index + 1); } } return formattedString; } public static string GetFieldValue(string name, object obj) { try { return obj.GetType().GetField(name).GetValue(obj).ToString(); } catch { return "{Unable to find: "+ name+"}" ; } } }
方案1:用表达式树缓存字段访问委托
反射的性能损耗主要在每次调用时的类型查找和字段获取,用表达式树编译一次字段访问逻辑并缓存,后续直接调用委托,能大幅提升性能。
实现代码:
public static class FormattedString { // 缓存字段访问委托,键为类型+字段名的组合 private static readonly Dictionary<string, Func<object, string>> _fieldAccessors = new Dictionary<string, Func<object, string>>(); public static string GetFormattedString(string baseString, object obj) { if (string.IsNullOrEmpty(baseString) || !baseString.Contains('{')) return baseString; // 改用StringBuilder优化字符串拼接,原+=会产生大量临时字符串 StringBuilder formattedString = new StringBuilder(); foreach (var split in baseString.Split('{')) { if (!split.Contains('}')) { formattedString.Append(split); } else { int index = split.IndexOf('}'); var fieldName = split.Substring(0, index); formattedString.Append(GetFieldValue(fieldName, obj)); if (index < split.Length - 1) formattedString.Append(split.Substring(index + 1)); } } return formattedString.ToString(); } public static string GetFieldValue(string name, object obj) { if (obj == null) return "{Unable to find: " + name + "}"; var type = obj.GetType(); var cacheKey = $"{type.FullName}.{name}"; if (!_fieldAccessors.TryGetValue(cacheKey, out var accessor)) { // 构建表达式树,编译成委托 var param = Expression.Parameter(typeof(object), "obj"); var cast = Expression.Convert(param, type); var field = Expression.Field(cast, name); var toString = Expression.Call(field, typeof(object).GetMethod("ToString")); var lambda = Expression.Lambda<Func<object, string>>(toString, param); accessor = lambda.Compile(); _fieldAccessors[cacheKey] = accessor; } try { return accessor(obj); } catch { return "{Unable to find: " + name + "}"; } } }
优点:无需修改原有实体类,第一次调用编译后后续性能接近直接字段访问,兼顾灵活性和性能。
方案2:让实体类实现自定义变量访问接口
如果可以修改目标类的代码,定义一个统一的变量访问接口,直接通过方法获取值,完全避免反射开销。
实现代码:
// 定义变量访问接口 public interface IVariableProvider { string GetVariable(string name); } // 目标实体类实现接口 public class UserInfo : IVariableProvider { public string Username; public int Age; public string GetVariable(string name) { return name switch { "Username" => Username, "Age" => Age.ToString(), _ => "{Unable to find: " + name + "}" }; } } // 修改格式化类 public static class FormattedString { public static string GetFormattedString(string baseString, IVariableProvider provider) { if (string.IsNullOrEmpty(baseString) || !baseString.Contains('{')) return baseString; StringBuilder formattedString = new StringBuilder(); foreach (var split in baseString.Split('{')) { if (!split.Contains('}')) { formattedString.Append(split); } else { int index = split.IndexOf('}'); var varName = split.Substring(0, index); formattedString.Append(provider.GetVariable(varName)); if (index < split.Length - 1) formattedString.Append(split.Substring(index + 1)); } } return formattedString.ToString(); } }
优点:性能最优,类型安全,可自定义变量的处理逻辑;缺点:需要修改所有目标实体类的代码,灵活性稍弱。
方案3:直接传递字典映射变量
如果变量集合是动态的,或者不想依赖实体类,直接用Dictionary<string, object>传递变量键值对,取值效率极高。
实现代码:
public static class FormattedString { public static string GetFormattedString(string baseString, Dictionary<string, object> variables) { if (string.IsNullOrEmpty(baseString) || !baseString.Contains('{')) return baseString; StringBuilder formattedString = new StringBuilder(); foreach (var split in baseString.Split('{')) { if (!split.Contains('}')) { formattedString.Append(split); } else { int index = split.IndexOf('}'); var varName = split.Substring(0, index); if (variables.TryGetValue(varName, out var value)) { formattedString.Append(value?.ToString()); } else { formattedString.Append("{Unable to find: " + varName + "}"); } if (index < split.Length - 1) formattedString.Append(split.Substring(index + 1)); } } return formattedString.ToString(); } }
优点:实现最简单,性能好,完全灵活;缺点:需要手动维护字典和变量的映射关系,适合临时或动态变量场景。
额外优化点
不管用哪种方案,都要把原代码中的字符串+=替换为StringBuilder,因为字符串是不可变类型,+=会频繁创建临时字符串,导致内存和性能损耗。
内容的提问来源于stack exchange,提问作者CardCastle Studio
相关产品推荐
相关产品推荐

