动态属性访问器
使用反射,我们可以创建一个动态的属性访问器(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();
}
}
导出结果:
Test,100
属性复制
类似数据映射和转换,见完整样例
配置系统
// 从配置文件动态设置对象属性
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;
}
特点
- 灵活性:可以在运行时动态访问和修改对象属性
- 通用性:适用于任何类型的对象
- 性能优化:缓存属性信息,避免重复反射
- 功能丰富:提供批量操作、验证、复制等功能
注意事项
- 性能考虑:虽然有缓存,但反射操作仍比直接访问慢
- 属性可访问性:注意属性的读写权限