C#通过反射实现动态属性访问器

[复制链接]
发表于 2026-1-2 02:50:38 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
利用反射,我们可以创建一个动态的属性访问器(Dynamic Property Accessor),允许我们在运行时访问和修改对象的属性
为什么要动态访问

为什么不直接访问,而用动态访问?


  • 直接访问实用于:

    • 编译时就知道要访问的属性
    • 寻求最高性能的场景
    • 简单的属性访问

  • 动态访问实用于:

    • 运行时才确定要访问的属性
    • 必要通用处置处罚逻辑的场景
    • 框架开发
    • 设置驱动的数据处置处罚
    • 动态数据映射和转换

动态访问固然性能略低,但提供了更大的机动性和通用性,特别得当开发框架和通用工具
完备样例

  1. using System.Reflection;
  2. using System.Text;
  3. public class DynamicPropertyAccessor<T> where T : class
  4. {
  5.     private readonly Dictionary<string, PropertyInfo> _properties;
  6.     public DynamicPropertyAccessor()
  7.     {
  8.         _properties = typeof(T).GetProperties()
  9.             .ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
  10.     }
  11.     public bool HasProperty(string propertyName)
  12.     {
  13.         return _properties.ContainsKey(propertyName);
  14.     }
  15.     public IEnumerable<string> GetPropertyNames()
  16.     {
  17.         return _properties.Keys;
  18.     }
  19.     public Type GetPropertyType(string propertyName)
  20.     {
  21.         if (_properties.TryGetValue(propertyName, out PropertyInfo propInfo))
  22.         {
  23.             return propInfo.PropertyType;
  24.         }
  25.         throw new ArgumentException($"Property '{propertyName}' not found in type {typeof(T).Name}.");
  26.     }
  27.     public bool IsPropertyWritable(string propertyName)
  28.     {
  29.         if (_properties.TryGetValue(propertyName, out PropertyInfo propInfo))
  30.         {
  31.             return propInfo.CanWrite;
  32.         }
  33.         throw new ArgumentException($"Property '{propertyName}' not found in type {typeof(T).Name}.");
  34.     }
  35.     public object GetPropertyValue(T obj, string propertyName)
  36.     {
  37.         if (obj == null)
  38.             throw new ArgumentNullException(nameof(obj));
  39.         if (string.IsNullOrEmpty(propertyName))
  40.             throw new ArgumentNullException(nameof(propertyName));
  41.         if (_properties.TryGetValue(propertyName, out PropertyInfo propInfo))
  42.         {
  43.             try
  44.             {
  45.                 return propInfo.GetValue(obj);
  46.             }
  47.             catch (Exception ex)
  48.             {
  49.                 throw new InvalidOperationException($"Error getting value for property {propertyName}", ex);
  50.             }
  51.         }
  52.         throw new ArgumentException($"Property '{propertyName}' not found in type {typeof(T).Name}.");
  53.     }
  54.     public void SetPropertyValue(T obj, string propertyName, object value)
  55.     {
  56.         if (obj == null)
  57.             throw new ArgumentNullException(nameof(obj));
  58.         if (string.IsNullOrEmpty(propertyName))
  59.             throw new ArgumentNullException(nameof(propertyName));
  60.         if (_properties.TryGetValue(propertyName, out PropertyInfo propInfo))
  61.         {
  62.             try
  63.             {
  64.                 if (!propInfo.CanWrite)
  65.                 {
  66.                     throw new InvalidOperationException($"Property {propertyName} is read-only.");
  67.                 }
  68.                 if (value != null && !propInfo.PropertyType.IsAssignableFrom(value.GetType()))
  69.                 {
  70.                     value = Convert.ChangeType(value, propInfo.PropertyType);
  71.                 }
  72.                 propInfo.SetValue(obj, value);
  73.             }
  74.             catch (Exception ex)
  75.             {
  76.                 throw new InvalidOperationException(
  77.                     $"Error setting value for property {propertyName}. Expected type: {propInfo.PropertyType.Name}", ex);
  78.             }
  79.         }
  80.         else
  81.         {
  82.             throw new ArgumentException($"Property '{propertyName}' not found in type {typeof(T).Name}.");
  83.         }
  84.     }
  85.     public void BatchUpdateProperties(T entity, Dictionary<string, object> updates)
  86.     {
  87.         if (entity == null)
  88.             throw new ArgumentNullException(nameof(entity));
  89.         if (updates == null)
  90.             throw new ArgumentNullException(nameof(updates));
  91.         foreach (var update in updates)
  92.         {
  93.             SetPropertyValue(entity, update.Key, update.Value);
  94.         }
  95.     }
  96.     public string Export(IEnumerable<T> items, string[] propertiesToExport)
  97.     {
  98.         if (items == null)
  99.             throw new ArgumentNullException(nameof(items));
  100.         if (propertiesToExport == null || propertiesToExport.Length == 0)
  101.             throw new ArgumentException("No properties specified for export", nameof(propertiesToExport));
  102.         var txt = new StringBuilder();
  103.         foreach (var item in items)
  104.         {
  105.             var values = propertiesToExport
  106.                 .Select(prop => GetPropertyValue(item, prop)?.ToString())
  107.                 .ToList();
  108.             txt.AppendLine(string.Join(",", values));
  109.         }
  110.         return txt.ToString();
  111.     }
  112.     public void CopyMatchingProperties<TTarget>(T source, TTarget target) where TTarget : class
  113.     {
  114.         if (source == null)
  115.             throw new ArgumentNullException(nameof(source));
  116.         if (target == null)
  117.             throw new ArgumentNullException(nameof(target));
  118.         var targetAccessor = new DynamicPropertyAccessor<TTarget>();
  119.         foreach (var prop in _properties.Values)
  120.         {
  121.             try
  122.             {
  123.                 if (targetAccessor.HasProperty(prop.Name))
  124.                 {
  125.                     var value = GetPropertyValue(source, prop.Name);
  126.                     targetAccessor.SetPropertyValue(target, prop.Name, value);
  127.                 }
  128.             }
  129.             catch (Exception ex)
  130.             {
  131.                 // 可以选择记录日志日志
  132.                 Console.WriteLine($"Error copying property {prop.Name}: {ex.Message}");
  133.             }
  134.         }
  135.     }
  136.     public (bool IsValid, List<string> MissingProperties) ValidateRequiredProperties(
  137.         T entity,
  138.         string[] requiredProperties)
  139.     {
  140.         if (entity == null)
  141.             throw new ArgumentNullException(nameof(entity));
  142.         if (requiredProperties == null || requiredProperties.Length == 0)
  143.             return (true, new List<string>());
  144.         var missingProperties = new List<string>();
  145.         foreach (var propName in requiredProperties)
  146.         {
  147.             if (!HasProperty(propName))
  148.             {
  149.                 missingProperties.Add($"Property '{propName}' does not exist");
  150.                 continue;
  151.             }
  152.             var value = GetPropertyValue(entity, propName);
  153.             if (value == null)
  154.             {
  155.                 missingProperties.Add(propName);
  156.             }
  157.         }
  158.         return (missingProperties.Count == 0, missingProperties);
  159.     }
  160. }
  161. class Product
  162. {
  163.     public int Id { get; set; }
  164.     public string Name { get; set; }
  165.     public decimal Price { get; set; }
  166. }
  167. internal class Program
  168. {
  169.     static void Main(string[] args)
  170.     {
  171.         try
  172.         {
  173.             var accessor = new DynamicPropertyAccessor<Product>();
  174.             var product = new Product { Id = 1, Name = "Laptop", Price = 999.99m };
  175.             var product2 = new Product();
  176.             // 测试属性访问
  177.             Console.WriteLine($"产品名称: {accessor.GetPropertyValue(product, "Name")}");
  178.             accessor.SetPropertyValue(product, "Price", 1099.99m);
  179.             Console.WriteLine($"更新后的价格: {product.Price}");
  180.             // 测试属性验证
  181.             var requiredNameField = new[] { "Name" };
  182.             var (isValidName, missingName) = accessor.ValidateRequiredProperties(product, requiredNameField);
  183.             if (!isValidName)
  184.             {
  185.                 Console.WriteLine($"缺少必要字段: {string.Join(", ", missingName)}");
  186.             }
  187.             var requiredSizeField = new[] { "Size" };
  188.             var (isValidSize, missingSize) = accessor.ValidateRequiredProperties(product, requiredSizeField);
  189.             if (!isValidSize)
  190.             {
  191.                 Console.WriteLine($"缺少必要字段: {string.Join(", ", missingSize)}");
  192.             }
  193.             // 测试批量更新
  194.             var updates = new Dictionary<string, object>
  195.             {
  196.                 { "Name", "New Laptop" },
  197.                 { "Price", 1299.99m }
  198.             };
  199.             accessor.BatchUpdateProperties(product, updates);
  200.             Console.WriteLine($"批量更新后 - 名称: {product.Name}, 价格: {product.Price}");
  201.             // 测试属性复制
  202.             accessor.CopyMatchingProperties(product, product2);
  203.             Console.WriteLine($"复制后的产品 - Id: {product2.Id}, Name: {product2.Name}, Price: {product2.Price}");
  204.             // 测试导出
  205.             var products = new List<Product> { product, product2 };
  206.             var export = accessor.Export(products, new[] { "Id", "Name", "Price" });
  207.             Console.WriteLine("\n导出结果:");
  208.             Console.WriteLine(export);
  209.             // 测试属性信息
  210.             Console.WriteLine("\n属性信息:");
  211.             foreach (var propName in accessor.GetPropertyNames())
  212.             {
  213.                 var propType = accessor.GetPropertyType(propName);
  214.                 var isWritable = accessor.IsPropertyWritable(propName);
  215.                 Console.WriteLine($"属性: {propName}, 类型: {propType.Name}, 可写: {isWritable}");
  216.             }
  217.         }
  218.         catch (Exception ex)
  219.         {
  220.             Console.WriteLine($"发生错误: {ex.Message}");
  221.         }
  222.         Console.WriteLine("\n按任意键退出...");
  223.         Console.ReadKey();
  224.     }
  225. }
复制代码
运行结果
  1. 产品名称: Laptop
  2. 更新后的价格: 1099.99
  3. 缺少必要字段: Property 'Size' does not exist
  4. 批量更新后 - 名称: New Laptop, 价格: 1299.99
  5. 复制后的产品 - Id: 1, Name: New Laptop, Price: 1299.99
  6. 导出结果:
  7. 1,New Laptop,1299.99
  8. 1,New Laptop,1299.99
  9. 属性信息:
  10. 属性: Id, 类型: Int32, 可写: True
  11. 属性: Name, 类型: String, 可写: True
  12. 属性: Price, 类型: Decimal, 可写: True
复制代码
应用场景举例

这种实现特别实用于:


  • 必要动态处置处罚对象属性的场景
  • ORM(对象关系映射)框架
  • 通用数据转换工具
  • 设置管理体系
  • 单元测试框架
利用场景:


  • 数据映射:在差别对象之间复制属性值
  • 通用数据处置处罚:处置处罚具有动态属性的对象
  • 设置体系:动态读写设置值
  • 序列化/反序列化:动态处置处罚对象属性
数据映射和转换

  1. // 不同对象间的属性复制
  2. public class SourceDTO
  3. {
  4.     public string Name { get; set; }
  5.     public decimal Price { get; set; }
  6. }
  7. public class TargetEntity
  8. {
  9.     public string Name { get; set; }
  10.     public decimal Price { get; set; }
  11. }
  12. internal class Program
  13. {
  14.     static void Main(string[] args)
  15.     {      
  16.         var dto = new SourceDTO { Name = "Test", Price = 100 };
  17.         var entity = new TargetEntity();
  18.         var sAccessor = new DynamicPropertyAccessor<SourceDTO>();
  19.         var tAccessor = new DynamicPropertyAccessor<TargetEntity>();
  20.         sAccessor.CopyMatchingProperties(dto, entity);
  21.         var targetEntities = new List<TargetEntity> { entity };
  22.         var export = tAccessor.Export(targetEntities, new[] { "Name", "Price" });
  23.         Console.WriteLine("\n导出结果:");
  24.         Console.WriteLine(export);
  25.         Console.ReadKey();
  26.     }
  27. }
复制代码
  1. 导出结果:
  2. Test,100
复制代码
属性复制

类似数据映射和转换,见完备样例
设置体系

  1. // 从配置文件动态设置对象属性
  2. public class AppSettings
  3. {
  4.     public string DatabaseConnection { get; set; }
  5.     public int Timeout { get; set; }
  6.     public bool EnableLogging { get; set; }
  7. }
  8. internal class Program
  9. {
  10.     // 模拟从配置文件读取配置
  11.     public static Dictionary<string, object> LoadConfigurationFromFile()
  12.     {
  13.         return new Dictionary<string, object>
  14.             {
  15.                 { "DatabaseConnection", "Config.ConnectionString" },
  16.                 { "Timeout", 30 },
  17.                 { "EnableLogging", true },
  18.             };
  19.     }
  20.     static void Main(string[] args)
  21.     {
  22.         // 使用示例
  23.         var settings = new AppSettings();
  24.         var configValues = LoadConfigurationFromFile(); // 返回 Dictionary<string, object>
  25.         var accessor = new DynamicPropertyAccessor<AppSettings>();
  26.         accessor.BatchUpdateProperties(settings, configValues);
  27.         var targetEntities = new List<AppSettings> { settings };
  28.         var export = accessor.Export(targetEntities, new[] { "DatabaseConnection", "Timeout", "EnableLogging" });
  29.         Console.WriteLine("\n导出结果:");
  30.         Console.WriteLine(export);
  31.         Console.ReadKey();
  32.     }
  33. }
复制代码
数据导入导出

  1. // CSV数据导入
  2. public void ImportFromCsv(string csvData)
  3. {
  4.     var accessor = new DynamicPropertyAccessor<Product>();
  5.     foreach (var line in csvData.Split('\n'))
  6.     {
  7.         var values = line.Split(',');
  8.         var product = new Product();
  9.         accessor.SetPropertyValue(product, "Name", values[0]);
  10.         accessor.SetPropertyValue(product, "Price", decimal.Parse(values[1]));
  11.         // 保存产品...
  12.     }
  13. }
复制代码
数据验证

见完备样例
通用数据处置处罚工具

  1. // 通用数据比较器
  2. public bool CompareObjects<T>(T obj1, T obj2, string[] propertiesToCompare) where T : class
  3. {
  4.     var accessor = new DynamicPropertyAccessor<T>();
  5.     foreach (var prop in propertiesToCompare)
  6.     {
  7.         var value1 = accessor.GetPropertyValue(obj1, prop);
  8.         var value2 = accessor.GetPropertyValue(obj2, prop);
  9.         if (!Equals(value1, value2)) return false;
  10.     }
  11.     return true;
  12. }
复制代码
特点



  • 机动性:可以在运行时动态访问和修改对象属性
  • 通用性:实用于任何范例的对象
  • 性能优化:缓存属性信息,克制重复反射
  • 功能丰富:提供批量操纵、验证、复制等功能
注意事项



  • 性能思量:固然有缓存,但反射操纵仍比直接访问慢
  • 属性可访问性:注意属性的读写权限
参考



  • C#: 利用反射和泛型的高级本领
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表