C#反射PropertyInfo返回Int32非实际属性名及属性缓存实现方法
问题说明
初始实现中传入数组类型fix参数时,直接调用fix.GetType().GetProperties()获取到的是数组类型本身的固有属性(如Length、LongLength等Int32类型属性),这就是运行时输出Int32关联属性名称的原因。处理非数组对象时逻辑正常,由于方法需要兼容多场景入参、无法使用重载,因此入参声明为object类型。
初始问题代码如下:
public void ExportResults(object fix, string fileName) { if (string.IsNullOrWhiteSpace(FileExport)) { MessageBox.Show("File export cannot be empty", "Error", MessageBoxButton.OK); } else if (!string.IsNullOrWhiteSpace(FileExport)) { StringBuilder sb = new StringBuilder(); using (var wbook = new XLWorkbook()) { var rec = (object[])fix; int col = 1; var ws = wbook.Worksheets.Add("Sheet1"); foreach (var propertyInfo in from propertyInfo in fix.GetType().GetProperties() where !propertyInfo.Name.Contains("ExtensionData") select propertyInfo) { int row = 1; ws.Cell(row++, col).Value = propertyInfo.Name; for (int i = 0; i < rec.Length; i++) { ws.Cell(row++, col).Value = propertyInfo.GetValue(rec[i]); } col++; } for (int i = 0; i < rec.Length; i++) { foreach (var propertyInfo in from propertyInfo in fix.GetType().GetProperties() where !propertyInfo.Name.Contains("ExtensionData") select propertyInfo) { sb.AppendLine($"{propertyInfo.Name}: {propertyInfo.GetValue(rec[i])}"); } sb.AppendLine(); } } } }
临时修复的问题
调整取属性的目标为数组内元素后,已经解决了数组固有属性被误读的问题,但该实现每次调用都会重复执行反射操作获取属性,性能较差;同时现有修复代码存在逻辑bug:遍历属性时累加prop索引取rec[prop]的类型,会导致属性与数组元素错位,当数组长度小于属性数量时会直接触发索引越界异常。
临时修复代码如下:
public void ExportResults(object fix, string fileName) { if (string.IsNullOrWhiteSpace(FileExport)) { MessageBox.Show("File export cannot be empty", "Error", MessageBoxButton.OK); } else if (!string.IsNullOrWhiteSpace(FileExport)) { StringBuilder sb = new StringBuilder(); using (var wbook = new XLWorkbook()) { var rec = (object[])fix; int col = 1; var prop = 0; var ws = wbook.Worksheets.Add("Sheet1"); foreach (var propertyInfo in from propertyInfo in rec[prop].GetType().GetProperties() where !propertyInfo.Name.Contains("ExtensionData") select propertyInfo) { int row = 1; ws.Cell(row++, col).Value = propertyInfo.Name; for (int i = 0; i < rec.Length; i++) { ws.Cell(row++, col).Value = propertyInfo.GetValue(rec[i]); } col++; prop++; } for (int i = 0; i < rec.Length; i++) { foreach (var propertyInfo in from propertyInfo in rec[i].GetType().GetProperties() where !propertyInfo.Name.Contains("ExtensionData") select propertyInfo) { sb.AppendLine($"{propertyInfo.Name}: {propertyInfo.GetValue(rec[i])}"); } sb.AppendLine(); } } } }
属性缓存实现方案
反射操作的开销主要来自运行时遍历类型元数据的过程,由于类型的属性信息在程序运行期间不会变更,只需要在第一次获取某类型的属性列表时执行一次反射,后续直接读取缓存结果即可,具体实现逻辑:
- 定义线程安全的静态缓存容器,以
Type为Key,过滤后的PropertyInfo数组为Value - 封装统一的属性获取方法,缓存不存在时执行反射+过滤逻辑并存入缓存,存在则直接返回缓存结果
- 修复原代码的逻辑bug,增加边界判断
完整实现代码:
using System.Collections.Concurrent; using System.Reflection; using ClosedXML.Excel; using System.Text; using System.Windows; // 类内部定义静态反射属性缓存 private static readonly ConcurrentDictionary<Type, PropertyInfo[]> _propertyCache = new ConcurrentDictionary<Type, PropertyInfo[]>(); /// <summary> /// 从缓存获取指定类型过滤后的属性列表 /// </summary> private static PropertyInfo[] GetCachedProperties(Type targetType) { return _propertyCache.GetOrAdd(targetType, type => type.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => !p.Name.Contains("ExtensionData")) .ToArray() ); } public void ExportResults(object fix, string fileName) { if (string.IsNullOrWhiteSpace(FileExport)) { MessageBox.Show("File export cannot be empty", "Error", MessageBoxButton.OK); return; } var rec = fix as object[]; if (rec == null || rec.Length == 0) { MessageBox.Show("No valid data to export", "Error", MessageBoxButton.OK); return; } StringBuilder sb = new StringBuilder(); using (var wbook = new XLWorkbook()) { // 取数组第一个元素的类型获取表头属性,避免索引错位 var headerType = rec[0].GetType(); var headerProperties = GetCachedProperties(headerType); int col = 1; var ws = wbook.Worksheets.Add("Sheet1"); // 写入表头和列数据 foreach (var prop in headerProperties) { int row = 1; ws.Cell(row++, col).Value = prop.Name; for (int i = 0; i < rec.Length; i++) { var val = prop.GetValue(rec[i]); ws.Cell(row++, col).Value = val == null ? string.Empty : val.ToString(); } col++; } // 生成文本明细 for (int i = 0; i < rec.Length; i++) { var currentItem = rec[i]; if (currentItem == null) continue; // 兼容数组内多态元素,单独取当前元素类型的属性缓存 var currentProps = GetCachedProperties(currentItem.GetType()); foreach (var prop in currentProps) { sb.AppendLine($"{prop.Name}: {prop.GetValue(currentItem)}"); } sb.AppendLine(); } // 补充原有文件保存逻辑即可 // wbook.SaveAs(Path.Combine(FileExport, fileName)); } }
优化效果
- 同类型数据第一次导出仅执行一次反射,后续导出直接读取缓存,反射开销降低90%以上
- 提前完成属性过滤逻辑,避免循环内重复判断
- 修复了原代码中数组越界、属性错位、空值未处理、校验后未中断执行的隐藏bug
内容的提问来源于stack exchange,提问作者extarlife
相关产品推荐
相关产品推荐

