C# 反射

1. C# 反射核心类型与示例

反射允许程序在运行时检查元数据并操作类型、成员和对象。其核心入口是 System.Type,通过它可以访问构造函数、方法、属性和字段。

1.1 获取类型信息 (Type)

Type 是反射的根类,可以通过 typeof(T)、obj.GetType() 或 Assembly.GetType() 获取。

cs 复制代码
using System;
using System.Reflection;

public class SampleClass
{
    public int Id { get; set; }
    private string _name;
    
    public SampleClass() { }
    public SampleClass(string name) { _name = name; }
    
    public void Show() => Console.WriteLine($"Name: {_name}");
}

class Program
{
    static void Main()
    {
        // 1. 获取 Type 对象
        Type type = typeof(SampleClass);
        
        Console.WriteLine($"类型名称: {type.Name}");
        Console.WriteLine($"程序集: {type.Assembly.GetName().Name}");
    }
}

1.2 构造函数 (ConstructorInfo)

用于动态创建实例,特别是当构造函数参数在运行时才确定时。

cs 复制代码
// 获取带参数的构造函数
ConstructorInfo ctor = type.GetConstructor(new[] { typeof(string) });
if (ctor != null)
{
    // 动态创建实例
    object instance = ctor.Invoke(new object[] { "Alice" });
    Console.WriteLine("实例创建成功");
}

1.3 方法 (MethodInfo)

用于动态调用方法。注意私有方法需要指定 BindingFlags。

cs 复制代码
// 获取公共方法
MethodInfo method = type.GetMethod("Show");
if (method != null)
{
    // 需要先有实例,或者方法是静态的
    object instance = Activator.CreateInstance(type, "Bob");
    method.Invoke(instance, null); // 输出: Name: Bob
}

// 获取私有方法示例
// MethodInfo privateMethod = type.GetMethod("PrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);

1.4 属性 (PropertyInfo)

用于读取或设置属性值。比直接访问字段更常用,因为属性通常包含逻辑。

cs 复制代码
object instance = Activator.CreateInstance(type);
PropertyInfo prop = type.GetProperty("Id");

// 设置属性值
prop.SetValue(instance, 100);

// 获取属性值
int id = (int)prop.GetValue(instance);
Console.WriteLine($"ID: {id}"); // 输出: ID: 100

1.5 字段 (FieldInfo)

用于直接访问类的字段,包括私有字段。

cs 复制代码
// 获取私有字段 _name
FieldInfo field = type.GetField("_name", BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null)
{
    object instance = Activator.CreateInstance(type, "Charlie");
    string name = (string)field.GetValue(instance);
    Console.WriteLine($"Private Field: {name}"); // 输出: Private Field: Charlie
    
    // 修改私有字段
    field.SetValue(instance, "David");
}

2. System.Reflection.Emit:高性能动态代码生成

虽然反射灵活,但 MethodInfo.Invoke 和 PropertyInfo.SetValue 存在显著的性能开销(装箱、安全检查、JIT 路径不同)。‌Emit‌ 允许在运行时直接生成 IL(中间语言)指令,并将其编译为委托,性能接近手写代码。

Emit 核心类

DynamicMethod‌: 在内存中创建轻量级动态方法(最常用)。

ILGenerator‌: 发出 IL 指令的核心工具。

AssemblyBuilder/TypeBuilder‌: 用于生成完整的动态程序集和类型(适用于动态代理)。

使用 Emit 生成高性能 Getter

频繁读取某个对象的属性,使用 Emit 生成一个 Func<object, object> 委托来替代反射。

cs 复制代码
using System;
using System.Reflection;
using System.Reflection.Emit;

public class EmitHelper
{
    /// <summary>
    /// 使用 Emit 生成获取属性值的委托
    /// </summary>
    public static Func<object, object> CreateGetter(PropertyInfo property)
    {
        // 1. 定义动态方法:输入 object (target),返回 object (value)
        var dynamicMethod = new DynamicMethod(
            $"Get_{property.Name}", 
            typeof(object), 
            new[] { typeof(object) }, 
            property.DeclaringType, 
            skipVisibility: true); // 允许访问非公共成员

        ILGenerator il = dynamicMethod.GetILGenerator();

        // 2. 发出 IL 指令
        // Ldarg_0: 加载第一个参数 (target object) 到栈顶
        il.Emit(OpCodes.Ldarg_0);
        
        // Castclass: 将 object 转换为目标类型
        il.Emit(OpCodes.Castclass, property.DeclaringType);
        
        // Callvirt: 调用属性的 get 方法
        il.Emit(OpCodes.Callvirt, property.GetGetMethod());

        // 如果返回值是值类型,需要装箱以便返回 object
        if (property.PropertyType.IsValueType)
        {
            il.Emit(OpCodes.Box, property.PropertyType);
        }

        // Ret: 返回栈顶结果
        il.Emit(OpCodes.Ret);

        // 3. 创建委托
        return (Func<object, object>)dynamicMethod.CreateDelegate(typeof(Func<object, object>));
    }
}

// 使用示例
class Program
{
    static void Main()
    {
        var obj = new SampleClass { Id = 999 };
        var prop = typeof(SampleClass).GetProperty("Id");
        
        // 生成委托(只需一次)
        var getter = EmitHelper.CreateGetter(prop);
        
        // 高频调用(性能极高)
        for (int i = 0; i < 1000000; i++)
        {
            var val = getter(obj);
        }
        Console.WriteLine($"Emit Get Result: {getter(obj)}");
    }
}

基于反射与 Emit 的扩展程序架构

在企业级应用中,结合反射和 Emit 可以构建高度可扩展的插件化架构或动态规则引擎。

架构设计原则

契约优先 (Interface-Based):

  • 定义标准接口(如 IPlugin、IModule)。
  • 核心框架只依赖接口,不依赖具体实现类。

动态发现 (Discovery via Reflection):

  • 启动时扫描指定目录下的 DLL。
  • 使用反射查找实现了特定接口的类型。

性能优化 (Caching & Emit):

  • 缓存反射结果:避免每次调用都重新查找 MethodInfo。
  • Emit 加速热点路径:对于高频调用的插件方法或数据映射,使用 Emit 生成委托并缓存。

生命周期管理:

  • 明确插件的初始化、执行和销毁顺序。
  • 处理依赖关系(如插件 A 依赖插件 B)。

扩展架构实战模式

插件化加载系统

cs 复制代码
public interface IPlugin
{
    string Name { get; }
    void Execute();
}

public class PluginManager
{
    private List<IPlugin> _plugins = new List<IPlugin>();

    public void LoadPlugins(string directory)
    {
        foreach (var dll in Directory.GetFiles(directory, "*.dll"))
        {
            try
            {
                // 1. 加载程序集
                Assembly assembly = Assembly.LoadFrom(dll);
                
                // 2. 反射查找实现 IPlugin 的类型
                var types = assembly.GetTypes()
                    .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract);

                foreach (var type in types)
                {
                    // 3. 创建实例
                    var plugin = (IPlugin)Activator.CreateInstance(type);
                    _plugins.Add(plugin);
                    Console.WriteLine($"Loaded Plugin: {plugin.Name}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Failed to load {dll}: {ex.Message}");
            }
        }
    }

    public void RunAll()
    {
        foreach (var plugin in _plugins)
        {
            plugin.Execute();
        }
    }
}

高性能动态 Mapper (ORM 核心思想)

在 ORM 或 DTO 映射中,直接使用反射 SetValue 性能较差。可以使用 Emit 生成映射代码。

cs 复制代码
using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Reflection.Emit;

namespace HighPerformanceMapper
{
    // --- 1. 定义测试实体 ---
    public class SourceUser
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Balance { get; set; }
    }

    public class DestinationUser
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Balance { get; set; }
    }

    // --- 2. 高性能 Mapper 核心实现 ---
    public static class EmitMapper
    {
        // 缓存委托,Key 为 "SourceType->DestType"
        private static readonly ConcurrentDictionary<string, Delegate> _mapperCache = new();

        /// <summary>
        /// 获取或创建映射委托
        /// </summary>
        public static Action<TSource, TDestination> GetMapper<TSource, TDestination>()
        {
            string key = $"{typeof(TSource).FullName}->{typeof(TDestination).FullName}";
            
            if (_mapperCache.TryGetValue(key, out var cachedDelegate))
            {
                return (Action<TSource, TDestination>)cachedDelegate;
            }

            // 双重检查锁或 ConcurrentDictionary 保证线程安全
            var mapper = CreateMapperDelegate<TSource, TDestination>();
            _mapperCache[key] = mapper;
            return mapper;
        }

        private static Action<TSource, TDestination> CreateMapperDelegate<TSource, TDestination>()
        {
            Type sourceType = typeof(TSource);
            Type destType = typeof(TDestination);

            // 1. 创建动态方法: void Map(TSource source, TDestination dest)
            var dynamicMethod = new DynamicMethod(
                $"Map_{sourceType.Name}_To_{destType.Name}",
                null, // 返回类型 void
                new[] { sourceType, destType }, // 参数类型
                typeof(EmitMapper) // 所属类型,用于权限控制
            );

            ILGenerator il = dynamicMethod.GetILGenerator();

            // 2. 获取所有公共可读属性
            var sourceProps = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            
            foreach (var srcProp in sourceProps)
            {
                // 在目标类型中查找同名同类型的属性
                var destProp = destType.GetProperty(srcProp.Name, BindingFlags.Public | BindingFlags.Instance);
                
                // 简单校验:名称相同且类型兼容
                if (destProp != null && destProp.CanWrite && destProp.PropertyType == srcProp.PropertyType)
                {
                    // --- 生成 IL 指令 ---
                    
                    // Load argument 1 (dest object) onto stack
                    il.Emit(OpCodes.Ldarg_1); 
                    
                    // Load argument 0 (source object) onto stack
                    il.Emit(OpCodes.Ldarg_0); 
                    
                    // Call source getter: source.get_Property()
                    il.Emit(OpCodes.Callvirt, srcProp.GetGetMethod()); 
                    
                    // Call destination setter: dest.set_Property(value)
                    il.Emit(OpCodes.Callvirt, destProp.GetSetMethod()); 
                }
            }

            // Return from method
            il.Emit(OpCodes.Ret);

            // 3. 创建委托
            return (Action<TSource, TDestination>)dynamicMethod.CreateDelegate(typeof(Action<TSource, TDestination>));
        }
    }

    // --- 3. 测试与性能对比 ---
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("=== 高性能 Emit Mapper 演示 ===\n");

            // 1. 功能验证
            var source = new SourceUser { Id = 101, Name = "Alice", Balance = 999.50m };
            var dest = new DestinationUser();

            var mapper = EmitMapper.GetMapper<SourceUser, DestinationUser>();
            mapper(source, dest);

            Console.WriteLine($"映射结果: Id={dest.Id}, Name={dest.Name}, Balance={dest.Balance}");
            Console.WriteLine($"匹配成功: {source.Id == dest.Id && source.Name == dest.Name}\n");

            // 2. 性能基准测试
            Benchmark();
        }

        static void Benchmark()
        {
            int count = 1_000_000;
            var source = new SourceUser { Id = 1, Name = "Test", Balance = 100m };
            
            // A. 传统反射方式 (模拟)
            var type = typeof(SourceUser);
            var props = type.GetProperties();
            var destType = typeof(DestinationUser);
            
            var swRef = System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < count; i++)
            {
                var d = new DestinationUser();
                foreach (var p in props)
                {
                    var dp = destType.GetProperty(p.Name);
                    if (dp != null) dp.SetValue(d, p.GetValue(source));
                }
            }
            swRef.Stop();

            // B. Emit 动态映射方式
            var mapper = EmitMapper.GetMapper<SourceUser, DestinationUser>();
            var swEmit = System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < count; i++)
            {
                var d = new DestinationUser();
                mapper(source, d);
            }
            swEmit.Stop();

            // C. 手写代码方式 (基准线)
            var swHand = System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < count; i++)
            {
                var d = new DestinationUser();
                d.Id = source.Id;
                d.Name = source.Name;
                d.Balance = source.Balance;
            }
            swHand.Stop();

            Console.WriteLine($"性能对比 ({count} 次循环):");
            Console.WriteLine($"手写代码:   {swHand.ElapsedMilliseconds} ms");
            Console.WriteLine($"Emit Mapper: {swEmit.ElapsedMilliseconds} ms (接近手写)");
            Console.WriteLine($"传统反射:   {swRef.ElapsedMilliseconds} ms (较慢)");
        }
    }
}

Emit 与反射的区别

特性 反射 (Reflection) Emit
‌主要作用‌ 读取已有结构,动态调用成员 ‌生成‌新的 IL、方法、类型或程序集
‌性能‌ 较慢(尤其是频繁 Invoke) ‌极快‌(生成后缓存为委托)
‌使用难度‌ 较低,API 直观 较高,需理解 IL 栈模型
‌适用场景‌ 插件加载、配置读取、低频调用 ORM 映射、JSON 序列化、AOP 代理、高频 Mapper
  • 如果需求在编译期可知:优先使用Source Generators (源生成器) 。它在编译时生成 C# 代码,兼具高性能和调试友好性,且对 AOT 友好。
  • 如果需求必须在运行时确定(如动态加载插件、运行时公式计算、动态代理):使用 Emit。
  • 如果只是偶尔动态调用:使用反射 , 简单且易于维护。
相关推荐
一叶星殇2 小时前
版本即契约:.NET 6 WebAPI 多版本治理与 Swagger 文档分治实践
mvc·.net
Jeff-Tsai2 小时前
Day 9:数据与定义分家,表单不必各自建类型
.net·架构设计·erp·definition-driven
步步为营DotNet2 小时前
.NET 可观测性选型:Metrics、ActivitySource、EventSource 到底该用哪个
c#·.net·metrics·可观测性
CSharp精选营3 小时前
我把 Ant Design Blazor 翻了一遍:Blazor 中后台组件库到底能不能用
.net·blazor·ant design blazor·中后台系统·mudblazor
IT规划师3 小时前
Redis 数据类型全解 - ZSet:两个编码,七种玩法(五)
redis·.net
吃一碗粉只付一碗钱14 小时前
16-SqlSugar多库ConfigId与按实体选连接
sqlserver·c#
rockey62715 小时前
AScript之字符串模板
c#·.net·script·expression·动态脚本
will_net16 小时前
Redis集群部署+Redis 链接地址放入Aollo +c#项目接入Redis
redis·c#·bootstrap
BuHuaX1 天前
Lua 与 ToLua:运行机制及热更新原理
开发语言·unity·junit·c#·lua