马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
利用反射,我们可以创建一个动态的属性访问器(Dynamic Property Accessor),允许我们在运行时访问和修改对象的属性
为什么要动态访问
为什么不直接访问,而用动态访问?
- 直接访问实用于:
- 编译时就知道要访问的属性
- 寻求最高性能的场景
- 简单的属性访问
- 动态访问实用于:
- 运行时才确定要访问的属性
- 必要通用处置处罚逻辑的场景
- 框架开发
- 设置驱动的数据处置处罚
- 动态数据映射和转换
动态访问固然性能略低,但提供了更大的机动性和通用性,特别得当开发框架和通用工具
完备样例
- using System.Reflection;
- using System.Text;
- public class DynamicPropertyAccessor<T> where T : class
- {
- private readonly Dictionary<string, PropertyInfo> _properties;
- public DynamicPropertyAccessor()
- {
- _properties = typeof(T).GetProperties()
- .ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
- }
- public bool HasProperty(string propertyName)
- {
- return _properties.ContainsKey(propertyName);
- }
- public IEnumerable<string> GetPropertyNames()
- {
- return _properties.Keys;
- }
- public Type GetPropertyType(string propertyName)
- {
- if (_properties.TryGetValue(propertyName, out PropertyInfo propInfo))
- {
- return propInfo.PropertyType;
- }
- throw new ArgumentException($"Property '{propertyName}' not found in type {typeof(T).Name}.");
- }
- public bool IsPropertyWritable(string propertyName)
- {
- if (_properties.TryGetValue(propertyName, out PropertyInfo propInfo))
- {
- return propInfo.CanWrite;
- }
- throw new ArgumentException($"Property '{propertyName}' not found in type {typeof(T).Name}.");
- }
- public object GetPropertyValue(T obj, string propertyName)
- {
- if (obj == null)
- throw new ArgumentNullException(nameof(obj));
- if (string.IsNullOrEmpty(propertyName))
- throw new ArgumentNullException(nameof(propertyName));
- if (_properties.TryGetValue(propertyName, out PropertyInfo propInfo))
- {
- try
- {
- return propInfo.GetValue(obj);
- }
- catch (Exception ex)
- {
- throw new InvalidOperationException($"Error getting value for property {propertyName}", ex);
- }
- }
- throw new ArgumentException($"Property '{propertyName}' not found in type {typeof(T).Name}.");
- }
- public void SetPropertyValue(T obj, string propertyName, object value)
- {
- if (obj == null)
- throw new ArgumentNullException(nameof(obj));
- if (string.IsNullOrEmpty(propertyName))
- throw new ArgumentNullException(nameof(propertyName));
- if (_properties.TryGetValue(propertyName, out PropertyInfo propInfo))
- {
- try
- {
- if (!propInfo.CanWrite)
- {
- throw new InvalidOperationException($"Property {propertyName} is read-only.");
- }
- if (value != null && !propInfo.PropertyType.IsAssignableFrom(value.GetType()))
- {
- value = Convert.ChangeType(value, propInfo.PropertyType);
- }
- propInfo.SetValue(obj, value);
- }
- catch (Exception ex)
- {
- throw new InvalidOperationException(
- $"Error setting value for property {propertyName}. Expected type: {propInfo.PropertyType.Name}", ex);
- }
- }
- else
- {
- throw new ArgumentException($"Property '{propertyName}' not found in type {typeof(T).Name}.");
- }
- }
- public void BatchUpdateProperties(T entity, Dictionary<string, object> updates)
- {
- if (entity == null)
- throw new ArgumentNullException(nameof(entity));
- if (updates == null)
- throw new ArgumentNullException(nameof(updates));
- foreach (var update in updates)
- {
- SetPropertyValue(entity, update.Key, update.Value);
- }
- }
- public string Export(IEnumerable<T> items, string[] propertiesToExport)
- {
- if (items == null)
- throw new ArgumentNullException(nameof(items));
- if (propertiesToExport == null || propertiesToExport.Length == 0)
- throw new ArgumentException("No properties specified for export", nameof(propertiesToExport));
- var txt = new StringBuilder();
- foreach (var item in items)
- {
- var values = propertiesToExport
- .Select(prop => GetPropertyValue(item, prop)?.ToString())
- .ToList();
- txt.AppendLine(string.Join(",", values));
- }
- return txt.ToString();
- }
- public void CopyMatchingProperties<TTarget>(T source, TTarget target) where TTarget : class
- {
- if (source == null)
- throw new ArgumentNullException(nameof(source));
- if (target == null)
- throw new ArgumentNullException(nameof(target));
- var targetAccessor = new DynamicPropertyAccessor<TTarget>();
- foreach (var prop in _properties.Values)
- {
- try
- {
- if (targetAccessor.HasProperty(prop.Name))
- {
- var value = GetPropertyValue(source, prop.Name);
- targetAccessor.SetPropertyValue(target, prop.Name, value);
- }
- }
- catch (Exception ex)
- {
- // 可以选择记录日志
 - Console.WriteLine($"Error copying property {prop.Name}: {ex.Message}");
- }
- }
- }
- public (bool IsValid, List<string> MissingProperties) ValidateRequiredProperties(
- T entity,
- string[] requiredProperties)
- {
- if (entity == null)
- throw new ArgumentNullException(nameof(entity));
- if (requiredProperties == null || requiredProperties.Length == 0)
- return (true, new List<string>());
- var missingProperties = new List<string>();
- foreach (var propName in requiredProperties)
- {
- if (!HasProperty(propName))
- {
- missingProperties.Add($"Property '{propName}' does not exist");
- continue;
- }
- var value = GetPropertyValue(entity, propName);
- if (value == null)
- {
- missingProperties.Add(propName);
- }
- }
- return (missingProperties.Count == 0, missingProperties);
- }
- }
- class Product
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public decimal Price { get; set; }
- }
- internal class Program
- {
- static void Main(string[] args)
- {
- try
- {
- var accessor = new DynamicPropertyAccessor<Product>();
- var product = new Product { Id = 1, Name = "Laptop", Price = 999.99m };
- var product2 = new Product();
- // 测试属性访问
- Console.WriteLine($"产品名称: {accessor.GetPropertyValue(product, "Name")}");
- accessor.SetPropertyValue(product, "Price", 1099.99m);
- Console.WriteLine($"更新后的价格: {product.Price}");
- // 测试属性验证
- var requiredNameField = new[] { "Name" };
- var (isValidName, missingName) = accessor.ValidateRequiredProperties(product, requiredNameField);
- if (!isValidName)
- {
- Console.WriteLine($"缺少必要字段: {string.Join(", ", missingName)}");
- }
- var requiredSizeField = new[] { "Size" };
- var (isValidSize, missingSize) = accessor.ValidateRequiredProperties(product, requiredSizeField);
- if (!isValidSize)
- {
- Console.WriteLine($"缺少必要字段: {string.Join(", ", missingSize)}");
- }
- // 测试批量更新
- var updates = new Dictionary<string, object>
- {
- { "Name", "New Laptop" },
- { "Price", 1299.99m }
- };
- accessor.BatchUpdateProperties(product, updates);
- Console.WriteLine($"批量更新后 - 名称: {product.Name}, 价格: {product.Price}");
- // 测试属性复制
- accessor.CopyMatchingProperties(product, product2);
- Console.WriteLine($"复制后的产品 - Id: {product2.Id}, Name: {product2.Name}, Price: {product2.Price}");
- // 测试导出
- var products = new List<Product> { product, product2 };
- var export = accessor.Export(products, new[] { "Id", "Name", "Price" });
- Console.WriteLine("\n导出结果:");
- Console.WriteLine(export);
- // 测试属性信息
- Console.WriteLine("\n属性信息:");
- foreach (var propName in accessor.GetPropertyNames())
- {
- var propType = accessor.GetPropertyType(propName);
- var isWritable = accessor.IsPropertyWritable(propName);
- Console.WriteLine($"属性: {propName}, 类型: {propType.Name}, 可写: {isWritable}");
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine($"发生错误: {ex.Message}");
- }
- Console.WriteLine("\n按任意键退出...");
- Console.ReadKey();
- }
- }
复制代码 运行结果
- 产品名称: Laptop
- 更新后的价格: 1099.99
- 缺少必要字段: Property 'Size' does not exist
- 批量更新后 - 名称: New Laptop, 价格: 1299.99
- 复制后的产品 - Id: 1, Name: New Laptop, Price: 1299.99
- 导出结果:
- 1,New Laptop,1299.99
- 1,New Laptop,1299.99
- 属性信息:
- 属性: Id, 类型: Int32, 可写: True
- 属性: Name, 类型: String, 可写: True
- 属性: Price, 类型: Decimal, 可写: True
复制代码 应用场景举例
这种实现特别实用于:
- 必要动态处置处罚对象属性的场景
- ORM(对象关系映射)框架
- 通用数据转换工具
- 设置管理体系
- 单元测试框架
利用场景:
- 数据映射:在差别对象之间复制属性值
- 通用数据处置处罚:处置处罚具有动态属性的对象
- 设置体系:动态读写设置值
- 序列化/反序列化:动态处置处罚对象属性
数据映射和转换
- // 不同对象间的属性复制
- public class SourceDTO
- {
- public string Name { get; set; }
- public decimal Price { get; set; }
- }
- public class TargetEntity
- {
- public string Name { get; set; }
- public decimal Price { get; set; }
- }
- internal class Program
- {
- static void Main(string[] args)
- {
- var dto = new SourceDTO { Name = "Test", Price = 100 };
- var entity = new TargetEntity();
- var sAccessor = new DynamicPropertyAccessor<SourceDTO>();
- var tAccessor = new DynamicPropertyAccessor<TargetEntity>();
- sAccessor.CopyMatchingProperties(dto, entity);
- var targetEntities = new List<TargetEntity> { entity };
- var export = tAccessor.Export(targetEntities, new[] { "Name", "Price" });
- Console.WriteLine("\n导出结果:");
- Console.WriteLine(export);
- Console.ReadKey();
- }
- }
复制代码 属性复制
类似数据映射和转换,见完备样例
设置体系
- // 从配置文件动态设置对象属性
- public class AppSettings
- {
- public string DatabaseConnection { get; set; }
- public int Timeout { get; set; }
- public bool EnableLogging { get; set; }
- }
- internal class Program
- {
- // 模拟从配置文件读取配置
- public static Dictionary<string, object> LoadConfigurationFromFile()
- {
- return new Dictionary<string, object>
- {
- { "DatabaseConnection", "Config.ConnectionString" },
- { "Timeout", 30 },
- { "EnableLogging", true },
- };
- }
- static void Main(string[] args)
- {
- // 使用示例
- var settings = new AppSettings();
- var configValues = LoadConfigurationFromFile(); // 返回 Dictionary<string, object>
- var accessor = new DynamicPropertyAccessor<AppSettings>();
- accessor.BatchUpdateProperties(settings, configValues);
- var targetEntities = new List<AppSettings> { settings };
- var export = accessor.Export(targetEntities, new[] { "DatabaseConnection", "Timeout", "EnableLogging" });
- Console.WriteLine("\n导出结果:");
- Console.WriteLine(export);
- Console.ReadKey();
- }
- }
复制代码 数据导入导出
- // CSV数据导入
- public void ImportFromCsv(string csvData)
- {
- var accessor = new DynamicPropertyAccessor<Product>();
- foreach (var line in csvData.Split('\n'))
- {
- var values = line.Split(',');
- var product = new Product();
- accessor.SetPropertyValue(product, "Name", values[0]);
- accessor.SetPropertyValue(product, "Price", decimal.Parse(values[1]));
- // 保存产品...
- }
- }
复制代码 数据验证
见完备样例
通用数据处置处罚工具
- // 通用数据比较器
- public bool CompareObjects<T>(T obj1, T obj2, string[] propertiesToCompare) where T : class
- {
- var accessor = new DynamicPropertyAccessor<T>();
- foreach (var prop in propertiesToCompare)
- {
- var value1 = accessor.GetPropertyValue(obj1, prop);
- var value2 = accessor.GetPropertyValue(obj2, prop);
- if (!Equals(value1, value2)) return false;
- }
- return true;
- }
复制代码 特点
- 机动性:可以在运行时动态访问和修改对象属性
- 通用性:实用于任何范例的对象
- 性能优化:缓存属性信息,克制重复反射
- 功能丰富:提供批量操纵、验证、复制等功能
注意事项
- 性能思量:固然有缓存,但反射操纵仍比直接访问慢
- 属性可访问性:注意属性的读写权限
参考
|