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

动态属性访问器

使用反射,我们可以创建一个动态的属性访问器(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;
}

特点

  • 灵活性:可以在运行时动态访问和修改对象属性
  • 通用性:适用于任何类型的对象
  • 性能优化:缓存属性信息,避免重复反射
  • 功能丰富:提供批量操作、验证、复制等功能

注意事项

  • 性能考虑:虽然有缓存,但反射操作仍比直接访问慢
  • 属性可访问性:注意属性的读写权限

参考

posted @   VinciYan  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
点击右上角即可分享
微信分享提示