>> >> >> Reference << << << <<<<<<Ref>>>>>>
Reflection
Modified: 2025-06-01 | Author:ljf12825

基本概念

反射是一种计算机编程语言的能力,它允许程序在运行时(Runtime)检查、分析甚至修改自身的结构(如类、方法、属性、接口等)的行为
反射像一面镜子
在编写代码的编译时(Compiletime),你很清晰程序的结构
而反射则让程序在运行时(Runtime)能够”照镜子“,看到自己由什么组成,并能动态地调用或操作这些部分\

代码中有许多类,每个类都包含了许多字段,现在提供一个类Type,可以获得任何一个类的任何字段(包括私有字段),提供查看,修改,创建功能

反射的作用

  1. 在运行时检查类型信息
  1. 在运行时操作类、对象和成员
  1. 晚期绑定(Late Binding)

反射的优缺点

优点

缺点:

反射使用场景

优先考虑编译时安全性和性能,仅在别无他法或收益远大于成本时,才在严格限制下使用反射\

  1. 构建基础设施、框架和工具 编写通用工具或辅助方法
    一个深度克隆(Deep Clone)工具方法。需要遍历一个对象的所有字段(包括私有字段),来创建一个完美的副本。这种方法通常会在项目的基础工具库中写一次,然后被广泛复用
    时刻问自己:“我是在编写像框架一样的通用基础设施吗?”如果答案是否定的,那么你很可能不应该使用反射。日常业务中,99.9%的情况都有更好的、不依赖反射的解决方案

  2. 与外部系统或动态配置集成 当你的程序需要根据外部配置(如配置文件、数据库中的设置)来动态决定行为时
    一个简单的插件系统。在配置文件中写上插件类的名称"MyApp.Plugins.EmailNotifier",主程序通过Type.GetType()创建并加载它。虽然用了反射,但它被隔离在程序启动的初始化阶段,不会影响核心性能
    反射操作比直接代码调用慢几个数量级。在性能关键的路径上使用反射是自杀行为

  3. 使用重度依赖反射的成熟库 很多时候,已经在“间接地”使用反射了,只是库帮你处理了所有复杂的细节,并做了性能优化

  4. 绝对尊重封装 仅仅因为能访问私有字段,不意味着应该这么做。修改私有成员是最后的手段,通常只用于测试、序列化等框架场景。滥用它会创建极其脆弱和难以维护的代码,因为代码与类的内部实现细节紧密耦合,一旦类的私有成员改变,代码就会立刻崩溃

  5. 安全风险 如果正在处理敏感数据(如密码、令牌),反射的存在意味着任何能运行代码的组件都可能提取这些数据。在设计安全敏感的系统时,必须考虑到这一点

C#反射核心

核心命名空间及其下的类

System.Type/TypeInfo

背景

TypeInfoType功能的扩展,是Type的增强视图,它提供了更丰富、更符合现代.NET开发模式的反射API,尤其是在.NET Core和现代应用模型中
在传统的、完整的.NET Framework中,反射的核心一直是System.Type类。所有关于类型的信息都通过它来获取
当微软开始开发.NET Core(一个跨平台、模块化、高性能的.NET版本)时,他们需要一个策略来让平台更轻量级。一个核心思想是按需付费:你的应用程序不应该被迫加载一个庞大的完整程序集,而是只加载它实际需要的部分
传统的System.Type位于mscorlib.dll(后来是System.Runtime.dll)中,它是一个非常庞大和全面的类型。为了支持这种模块化,反射API被拆分和重新设计
System.Type是一个装工具的大工具箱。TypeInfo就像是把这个工具箱打开,把里面的工具(方法、属性、基类等信息)都拿出来,整齐地摆在你面前,让你看得更清楚,用起来也更符合“按需取用”的原则
**System.Type vs TypeInfo

特性System.Type(传统方式)TypeInfo(现代方式)
所在程序集System.RuntimeSystem.Reflection
设计哲学“拉”(Pull)。你向Type请求信息(例如,调用.GetMethods(),它才会去加载并返回“展”(Expose)API。类型的信息已经作为属性展示在TypeInfo对象上了
成员访问通过方法获取数组成员(如GetMethods(), GetProperties()通过属性获取集合成员(如DeclaredMethods, DeclaredProperties),这些集合是预计算好的
延迟加载√ 当你调用GetMethod()时,它才去计算并返回结果概念上更倾向于提前计算。TypeInfo对象本身就承载着这些信息集合
主要使用场景传统的.NET Framework应用.NET Core+、.NET 5+、UWP、Xamarin等现代应用模型。特别是依赖于Microsoft.Extensions.DependencInjection的应用(如ASP.NET Core)

System.Type

获取
  1. typeof运算符:当你在编译时就知道类型时
Type stringType = typeof(string);
Type myClassType = typeof(MyClass);
  1. Object.GetType()实例方法:当你有一个对象实例时
Person person = new Person();
Type personType = person.GetType(); // 获取 person 实例的运行时类型
  1. Type.GetType()静态方法:当你只有类型的完全限定名称字符串时(最动态的方式)
// 格式:“命名空间.类名,程序集名”
Type type = Type.GetType("System.String, mscorlib"); // 获取string的类型
Type type2 = Type.GetType("MyNamespace.MyClass, MyAssembly");
内容

Type类包含了海量的信息,以下分类列出最重要和最常用的部分

  1. 类型标识信息(Identity Information) 这些属性告诉你这个类型“是谁”
  1. 成员discovery(发现成员) 这是反射最核心的功能之一:获取类型的所有成员。这些方法通常返回XXXInfo类型的数组

以上方法通常有重载版本,接收BindingFlags枚举参数,让你能精确控制搜索范围(例如,查找私有成员、静态成员等)

  1. 泛型信息(Generic Information) 对于泛型类型,Type提供了专门的处理
  1. 接口实现(Interface Implementation)
  1. 创建实例与获取特定成员
object myInstance = Activator.CreateInstance(myType);
  1. 特性(Attributes)信息
var attributes = myType.GetCustomAttributes(typeof(ObsoleteAttribute), false);
if (attribute.Length > 0) Console.WriteLine("This class is obsolete!")
  1. 判断类型特征属性(Isxxx类型本质
属性说明示例
IsClass是否为类(不含接口、值类型)typeof(string).IsClass → true
IsInterface是否为接口typeof(IDisposable).IsInterface → true
IsValueType是否为值类型(struct/enum)typeof(int).IsValueType → true
IsEnum是否为枚举typeof(DayOfWeek).IsEnum → true
IsPrimitive是否为基元类型(int, bool, char 等)typeof(int).IsPrimitive → true
IsArray是否为数组typeof(int[]).IsArray → true
IsPointer是否为指针类型typeof(int*).IsPointer → true
IsByRef是否为 ref 传递类型typeof(int).MakeByRefType().IsByRef → true
IsSubclassOf(Type)是否为某类型的子类typeof(List<int>).IsSubclassOf(typeof(object)) → true

类修饰符相关

属性说明示例
IsAbstract是否为抽象类/抽象方法typeof(Stream).IsAbstract → true
IsSealed是否为密封类(sealed)typeof(string).IsSealed → true
IsPublic是否为公共类型typeof(string).IsPublic → true
IsNotPublic是否为非公共类型内部类等
IsVisible是否对外可见(公共并且程序集可见性允许)

嵌套类可见性
当类型时嵌套类才有意义

属性说明
IsNestedPublic公共嵌套类
IsNestedPrivate私有嵌套类
IsNestedFamilyprotected 嵌套类
IsNestedAssemblyinternal 嵌套类
IsNestedFamANDAssemprotected internal 且要在同一程序集中
IsNestedFamORAssemprotected internal 或程序集可见

泛型相关

属性说明示例
IsGenericType是否为泛型类型typeof(List<int>).IsGenericType → true
IsGenericTypeDefinition是否为开放泛型定义typeof(List<>).IsGenericTypeDefinition → true
IsConstructedGenericType是否为已构造泛型(有具体类型参数)typeof(List<int>).IsConstructedGenericType → true
ContainsGenericParameters是否还包含未绑定的泛型参数typeof(Dictionary<,>).ContainsGenericParameters → true
IsGenericParameter是否为泛型参数本身T 这样的泛型占位符时才为 true

高级/特殊情况

属性说明
IsCOMObject是否为 COM 对象类型
IsMarshalByRef是否为按引用封送(Remoting 相关)
IsSecurityCritical / IsSecuritySafeCritical / IsSecurityTransparent与安全模型相关
IsSerializable是否可序列化
IsImport是否使用 ComImportAttribute 标记

TypeInfo

获取
using System.Reflection;

Type type = typeof(string); // 先获取普通Type对象

TypeInfo typeInfo = type.GetTypeInfo(); // 通过Type对象获取增强视图TypeInfo

Console.WriteLine(typeInfo.FullName);
foreach (var method in typeInfo.DeclaredMethods)
    Console.WriteLine(method.Name);

这是一种优雅的向后兼容策略。现有的代码仍然使用Type,完全不受影响。而新的、面向现代平台的代码可以轻松地切花电脑更强大的TypeInfoAPI

内容

TypeInfo不仅包含了Type的所有功能(通过其基类),还添加了更符合逻辑的分组和查询方式

  1. 声明成员(Declared Members) 返回直接在该类型上声明的成员,而不是从基类继承来的
  1. 更清晰的继承链分析
  1. 泛型信息的扩展
  1. Attributes信息
  1. 程序集和模块信息
核心价值
  1. 声明视图:提供了DeclaredXxx属性,让你能专注于类型自身声明的成员,过滤掉继承的“噪音”,这是它相对于直接使用TypeGetMethods()等方法的巨大优势
  2. LINQ友好:其属性大多返回IEnumerable<T>集合,可以无缝于LINQ查询集成,使得对类型元数据的查询、过滤和投影操作变得非常简洁和强大
  3. 现代API设计:它代表了.NET反射和API的现代化方向,更侧重于暴露数据而不是通过方法调用获取数据,这更符合.NET Core的轻量级和可组合性理念
  4. 功能完备:因为它继承自Type,所以可以在任何需要Type对象的地方使用TypeInfo,同时还能享受这些新增功能的强大

在现代.NET开发中(.NET5+),当需要进行复杂的类型内省时,TypeInfo应该是首先工具,而传统的Type方法则更适合简单的、一次性的反射操作

System.Reflection

System.Reflection是.NET Runtime的一个内省(introspection)和交互工具包,提供了程序在运行时检查、发现和操作程序集、模块、类型及其成员的所有能力

这个命名空间包含的类的能力:

  1. 加载和分析程序集(Assembly
  2. 检查和遍历类型结构(Type, TypeInfo
  3. 操作类型成员:方法、属性、字段、构造函数、事件等
  4. 处理泛型和自定义特性

关键类和结构

Assembly

程序集的代表。反射的起点之一。可以用它来加载程序集、获取其中定义的所有类型、获取程序集信息(名称、版本、文化等) Assembly.LoadFrom("MyLibrary.dll"):从文件路径加载程序集 Assembly.GetExecutingAssembly():获取当前正在执行的代码所在的程序集 assembly.GetTypes():获取程序集中定义的所有类型

Module

代表程序集内的一个模块(.netmodule)。程序集由一个或多个模块组成,但在日常开发中很少使用,通常一个程序集就是一个模块

Type & TypeInfo

类型的代表。这是反射最核心的类,包含了关于类型的所有元数据。TypeInfoType的扩展视图,在现代.NET中提供更丰富的API

MemberInfo派生类

这些类提供了对类型成员的具体操作能力,它们都继承自MemberInfo

其他

System.Reflection.Emit

System.Reflection的作用是检查和分析已有代码元数据,System.Reflection.Emit就是让在运行时动态地创建和生成全新的代码
它提供了在内存中或磁盘上动态构建新Assembly、Module、Type and Method的能力,甚至可以直接生成IL指令

核心思想:运行时代码生成

不是在VS里写C#代码然后编译成.dll,而是用C#代码编写一个程序,这个程序本身会像一个编译器一样,在运行时输出另一个.dll或在内存中创建一个新的类型。这就是Reflection.Emit所做的事

关键类

这个命名空间包含一整套用于按层次结构构建程序集的类

AssemblyBuilder, ModuleBuilder
TypeBuilder, EnumBuilder
MethodBuilder, ConstructorBuilder, FieldBuilder, PropertyBuilder, EventBuilder

这些类用于为动态类型添加成员你

ILGenerator

编写实际代码,这是最底层的,当使用MethodBuilderConstructorBuilder定义了方法的签名后,需要一个ILGenerator来填充方法的实际逻辑

常见的IL指令示例

主要用途和应用场景

  1. 动态代理(AOP - Aspect-Oriented Programming)
  1. ORM和序列化器的动态实现
  1. 编译器或脚本引擎
  1. 动态生成高性能代码:

总结

System.Reflection.Emit是一个极其强大但也非常复杂的底层API。它让你从”代码的消费者”变成了“代码的生产者”

因此,绝大多数普通开发者不会直接使用它,而是使用基于它构建的高级库。它的主要用户是框架、库和编译器开发者

System.Reflection.Metadata

System.Refelction.Metadata是一个更现代、更轻量级、更高性能的用于读取.NET程序集元数据的API。它提供了对ECMA-335(.NET程序集标准格式)的低级访问

核心思想:低级、只读、高性能

System.Reflection不同,System.Reflection.Metadata

  1. 只读(Read-Only):它只用于读取元数据,不能用于动态创建或修改类型(那是System.Reflection.Emit的工作)
  2. 低级(Low-Level):它不提供高层的抽象(如TypeMethodInfo对象),而是提供对元数据表、对、Blob的直接访问。需要自己组装出需要的信息
  3. 高性能(High-Performance):因为它避免了创建大对象(如为每个方法创建一个MethodInfo实例)和复杂的逻辑,所以它的速度极快,内存占用极低
  4. 无负载(No Load):它不会将程序集假爱到当前的AppDomainAssemblyLoadContext中。它只是直接解析程序集的字节流。这意味着它更安全,不会导致程序集冲突或锁定文件

关键概念和核心类

要理解这个命名空间,需要先了解ECMA-335元数据格式的几个核心概念

MetadataReader

这是整个API的核心和入口点。通过它来访问程序集的所有元数据

XXXDefinitionXXXDefinitionHandle

对于每种元数据元素,通常有一对类:

例如:

其他重要类型

如果用传统的System.Reflection,代码会简单得多:Assembly.LoadFrom("MyLibrary.dll").GetTypes()。但代价是加载了整个程序集,消耗了更多资源和时间

主要用途和应用场景

  1. 编译器和分析器(Roslyn)
  1. 高级代码分析工具
  1. IDE功能
  1. 序列化器/映射器的高级优化
  1. 安全的元数据检查

原类型如何避免被反射

  1. SecurityCriticalSecuritySafeCritical特性(在部分信任环境中) 在旧的.NET Framework代码访问安全(CAS)模型中,可以将代码标记为安全关键的,这会在部分信任的沙箱环境中阻止非特权代码通过反射进行访问。不过,这套模型在现代.NET中已经被认为过时且很少使用

  2. 运行时检查并拒绝 理论上,可以在属性的setter或方法里检查是谁在调用它。.NET提供了StackTraceCallingAssembly等类来检查调用堆栈

public string SecretPassword
{
  set
  {
    var stackTrace = new StackTrace();
    var callingMethod = stacckTrace.GetFrame(1)?.GetMethod();

    // 如果调用者是通过反色和来的,可以拒绝
    if (callingMethod?.Name == "Invoke" && callingMethod.DeclaringType?.Name == "RuntimeMethodInfo")
    {
      throw new InvalidOperationException("Direct assignment only! No reflection allowed!");
    }
    _secretPassword = value;
  }
}

但是这种方法非常脆弱、有性能开销,而且很容易被绕过(例如,反射调用底层私有字段_secretPassword就直接绕过了这个属性setter),因此极不推荐

反射在Unity中的使用

性能优化

反射与委托结合

反射与泛型结合

反射与LINQ结合

最佳实践

底层原理

底层理解

MethodInfo.Invoke, Property.SetValue, Property.GetValue

反射链路