首页 > 编程语言 >C#通过反射实现动态属性访问器

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

时间:2024-11-01 16:12:37浏览次数:1  
标签:反射 Name C# propertyName var new public string 属性

动态属性访问器

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

特点

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

注意事项

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

参考

标签:反射,Name,C#,propertyName,var,new,public,string,属性
From: https://www.cnblogs.com/vinciyan/p/18520466

相关文章

  • Office、Visio、project 各版本资源下载
    1、Office安装包资源下载(部分需要联网以及安装包内包含激活工具)   Office365:链接:https://pan.quark.cn/s/e680210d9869提取码:e8vjOffice2003:链接:https://pan.quark.cn/s/e2fb7135c8fc提取码:FgueOffice2010:链接:https://pan.quark.cn/s/4de780bbf20c提取码:Kb8gOf......
  • wincc中VBS添加对象
    定义变量DimMTX2将Hmiruntime的画面里面的控件赋给定义好的变量mtx2.改变对象属性,这里只改变颜色,还可以改变位置,大小等等。最后效果代码SubVBFunction_4()'提示:'1.使用<CTRL+SPACE>或<CTRL+I>快捷键打开含所有对象和函数的列表'2.使用HMIRuntime对象......
  • 华为云开源项目Sermant正式成为CNCF官方项目
    近日,云原生计算基金会(CNCF)正式接纳由华为云发起的云原生无代理服务网格项目Sermant。Sermant的加入,极大地丰富了云原生微服务治理技术的探索、创新和发展,为CNCF社区注入了新的活力。 Sermant是华为云在微服务治理技术领域多年的技术积累和丰富的实践经验孵化而来,致力于解决大......
  • spring-boot-configuration-processor无法生效
    引入了依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId></dependency>编译项目之后并没有生成target/classes/META-INF/spring-configuration-metadata.json看......
  • 如何优雅地从Lxc镜像偷rootfs
    首先我们拿到一个lxc镜像的链接,咱觉得bfsu的速度就很不错。https://mirrors.bfsu.edu.cn/lxc-images/images点进去,顺着目录就能找到rootfs.tar.xz,下载就完了。本文完,就这么简单。当然不是啊喂!我们要做到自动获取rootfs链接。镜像列表:LXC_MIRROR_MAIN="http://images.linuxc......
  • 浅析Dockerhub API:如何优雅地从dockerhub偷rootfs镜像
    成品:https://github.com/Moe-hacker/docker_image_puller前言:八月初的时候,咱无聊去扒了下dockerhub的接口,想通过网络请求直接从dockerhub偷镜像。然后写完才想起来dockkerhub在国内是被墙的,似乎这么一个功能用处也不大。。。。。然后咱就去旅游了,连项目Readme都没写(逃)。至于......
  • 内置RC振荡器/抗干扰能力强VK1668 SOP24数码管驱动控制器/LED驱动器原厂技术支持
    产品品牌:永嘉微电/VINKA产品型号:VK1668封装形式:SOP24概述VK1668是一种带键盘扫描接口的数码管或点阵LED驱动控制专用芯片,内部集成有3线串行接口、数据锁存器、LED驱动、键盘扫描等电路。SEG脚接LED阳极,GRID脚接LED阴极,可支持13SEGx4GRID、12SEGx5GRID、11SEGx6GRID、10S......
  • C语言的一些Hacking写法
    很显然,这些写法大多并不规范,也不被提倡。很显然,咱并没有在windows下试过这些代码,而且实测大部分在线编程网站用的是Linux,可以接受GNUC扩展支持。如果有人问我为什么折腾,为什么以折腾这些无聊的东西作为目标,那他们完全可以问,为什么要登上最高峰?为什么人类要登月?………我选择去折......
  • 国产物理密钥Canokey踩坑记录
    前段时间咱本着再不买以后就买不到了的心态购入了国产物理密钥Canokey,不得不说这价格是真的坚挺,至死不降那种。。。闪烁的蓝灯,优雅的签名,逼格算是拉满了,不过使用过程是真的曲折坎坷。咱主要是买来用于git签名与ssh认证的,配置过程前辈们已经写的很清楚了,写好了有奖励,写不好有惩罚......
  • CSP2024 游记
    又是一年CSP。。。10月5日,终于过S初赛了。。。然后开始漫长的备战。。在考试开始前1day,我还在兢兢业业地学习图论。然后发现没有考。。。10月25日下午15:30,来到CQBS试机。我想,怎么测试性能呢?于是就打开了florr在xxboyxx的加持下,florr连续合成四个红......